#!/usr/bin/env python # run a jsbsim model as a child process import sys, os, pexpect, socket import math, time, select, struct, signal, errno sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'pysim')) import util, atexit, fdpexpect from pymavlink import fgFDM class control_state(object): def __init__(self): self.aileron = 0 self.elevator = 0 self.throttle = 0 self.rudder = 0 self.ground_height = 0 sitl_state = control_state() def interpret_address(addrstr): '''interpret a IP:port string''' a = addrstr.split(':') a[1] = int(a[1]) return tuple(a) def jsb_set(variable, value): '''set a JSBSim variable''' global jsb_console jsb_console.send('set %s %s\r\n' % (variable, value)) def setup_template(home): '''setup aircraft/Rascal/reset.xml''' global opts v = home.split(',') if len(v) != 4: print("home should be lat,lng,alt,hdg - '%s'" % home) sys.exit(1) latitude = float(v[0]) longitude = float(v[1]) altitude = float(v[2]) heading = float(v[3]) sitl_state.ground_height = altitude template = os.path.join('aircraft', 'Rascal', 'reset_template.xml') reset = os.path.join('aircraft', 'Rascal', 'reset.xml') xml = open(template).read() % { 'LATITUDE' : str(latitude), 'LONGITUDE' : str(longitude), 'HEADING' : str(heading) } open(reset, mode='w').write(xml) print("Wrote %s" % reset) baseport = int(opts.simout.split(':')[1]) template = os.path.join('jsb_sim', 'fgout_template.xml') out = os.path.join('jsb_sim', 'fgout.xml') xml = open(template).read() % { 'FGOUTPORT' : str(baseport+3) } open(out, mode='w').write(xml) print("Wrote %s" % out) template = os.path.join('jsb_sim', 'rascal_test_template.xml') out = os.path.join('jsb_sim', 'rascal_test.xml') xml = open(template).read() % { 'JSBCONSOLEPORT' : str(baseport+4) } open(out, mode='w').write(xml) print("Wrote %s" % out) def process_sitl_input(buf): '''process control changes from SITL sim''' control = list(struct.unpack('<14H', buf)) pwm = control[:11] (speed, direction, turbulance) = control[11:] global wind wind.speed = speed*0.01 wind.direction = direction*0.01 wind.turbulance = turbulance*0.01 aileron = (pwm[0]-1500)/500.0 elevator = (pwm[1]-1500)/500.0 throttle = (pwm[2]-1000)/1000.0 if opts.revthr: throttle = 1.0 - throttle rudder = (pwm[3]-1500)/500.0 if opts.elevon: # fake an elevon plane ch1 = aileron ch2 = elevator aileron = (ch2-ch1)/2.0 # the minus does away with the need for RC2_REV=-1 elevator = -(ch2+ch1)/2.0 if opts.vtail: # fake an elevon plane ch1 = elevator ch2 = rudder # this matches VTAIL_OUTPUT==2 elevator = (ch2-ch1)/2.0 rudder = (ch2+ch1)/2.0 buf = '' if aileron != sitl_state.aileron: buf += 'set fcs/aileron-cmd-norm %s\n' % aileron sitl_state.aileron = aileron if elevator != sitl_state.elevator: buf += 'set fcs/elevator-cmd-norm %s\n' % elevator sitl_state.elevator = elevator if rudder != sitl_state.rudder: buf += 'set fcs/rudder-cmd-norm %s\n' % rudder sitl_state.rudder = rudder if throttle != sitl_state.throttle: buf += 'set fcs/throttle-cmd-norm %s\n' % throttle sitl_state.throttle = throttle buf += 'step\n' global jsb_console jsb_console.send(buf) def update_wind(wind): '''update wind simulation''' (speed, direction) = wind.current() jsb_set('atmosphere/psiw-rad', math.radians(direction)) jsb_set('atmosphere/wind-mag-fps', speed/0.3048) def process_jsb_input(buf, simtime): '''process FG FDM input from JSBSim''' global fdm, fg_out, sim_out fdm.parse(buf) if fg_out: try: agl = fdm.get('agl', units='meters') fdm.set('altitude', agl+sitl_state.ground_height, units='meters') fdm.set('rpm', sitl_state.throttle*1000) fg_out.send(fdm.pack()) except socket.error as e: if e.errno not in [ errno.ECONNREFUSED ]: raise timestamp = int(simtime*1.0e6) simbuf = struct.pack(' 0.1: update_wind(wind) last_wind_update = tnow if tnow - last_report > 3: print("FPS %u asl=%.1f agl=%.1f roll=%.1f pitch=%.1f a=(%.2f %.2f %.2f) AR=%.1f" % ( frame_count / (time.time() - last_report), fdm.get('altitude', units='meters'), fdm.get('agl', units='meters'), fdm.get('phi', units='degrees'), fdm.get('theta', units='degrees'), fdm.get('A_X_pilot', units='mpss'), fdm.get('A_Y_pilot', units='mpss'), fdm.get('A_Z_pilot', units='mpss'), achieved_rate)) frame_count = 0 last_report = time.time() if new_frame: now = time.time() if now < last_wall_time + scaled_frame_time: dt = last_wall_time+scaled_frame_time - now time.sleep(last_wall_time+scaled_frame_time - now) now = time.time() if now > last_wall_time and now - last_wall_time < 0.1: rate = 1.0/(now - last_wall_time) achieved_rate = (0.98*achieved_rate) + (0.02*rate) if achieved_rate < opts.rate*opts.speedup: scaled_frame_time *= 0.999 else: scaled_frame_time *= 1.001 last_wall_time = now def exit_handler(): '''exit the sim''' print("running exit handler") signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, signal.SIG_IGN) # JSBSim really doesn't like to die ... if getattr(jsb, 'pid', None) is not None: os.kill(jsb.pid, signal.SIGKILL) jsb_console.send('quit\n') jsb.close(force=True) util.pexpect_close_all() sys.exit(1) signal.signal(signal.SIGINT, exit_handler) signal.signal(signal.SIGTERM, exit_handler) try: main_loop() except Exception as ex: print(ex) exit_handler() raise