Skip to content

Commit

Permalink
Add Controls interface (v27) and simple_ctrls.py example
Browse files Browse the repository at this point in the history
  • Loading branch information
julianneswinoga committed Feb 12, 2023
1 parent 7d38efd commit 31328ea
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 2 deletions.
43 changes: 43 additions & 0 deletions examples/simple_ctrls.py
@@ -0,0 +1,43 @@
#!/usr/bin/python3
"""
Simple Controls example that sweeps the control surfaces and periodically puts the gear up/down
"""
import time
import math
from flightgear_python.fg_if import CtrlsConnection

gear_down_child_state = True
def ctrls_callback(ctrls_data, event_pipe):
global gear_down_child_state
if event_pipe.child_poll():
gear_down_child, = event_pipe.child_recv() # unpack tuple
# TODO: FG sometimes ignores "once" updates? i.e. if we set `ctrls_data.gear_handle`
# the next callback will still have the old value of `ctrls_data.gear_handle`, not
# the one that we set. To fix this we can just keep our own state of what the value
# should be and set it every time. I still need to figure out a clean way to fix
# this on the backend
gear_down_child_state = gear_down_child
ctrls_data.gear_handle = 'down' if gear_down_child_state else 'up'
# set only the data that we need to
ctrls_data.aileron = math.sin(time.time())
ctrls_data.elevator = math.sin(time.time())
ctrls_data.rudder = math.sin(time.time())
ctrls_data.throttle[0] = (math.sin(time.time()) / 2) + 0.5
ctrls_data.throttle[1] = (-math.sin(time.time()) / 2) + 0.5
return ctrls_data # return the whole structure

"""
Start FlightGear with `--native-ctrls=socket,out,30,,5503,udp --native-ctrls=socket,in,30,,5504,udp`
"""
ctrls_conn = CtrlsConnection(ctrls_version=27)
ctrls_event_pipe = ctrls_conn.connect_rx('localhost', 5503, ctrls_callback)
ctrls_conn.connect_tx('localhost', 5504)
ctrls_conn.start() # Start the Ctrls RX/TX loop

gear_down_parent = True
time.sleep(2)
while True:
# could also do `ctrls_conn.event_pipe.parent_send` so you just need to pass around `ctrls_conn`
ctrls_event_pipe.parent_send((gear_down_parent,)) # send tuple
gear_down_parent = not gear_down_parent # Flip gear state
time.sleep(10)
89 changes: 89 additions & 0 deletions flightgear_python/ctrls_v27.py
@@ -0,0 +1,89 @@
"""
FlightGear Controls Network interface, version 27
See https://github.com/FlightGear/flightgear/blob/619226e9d069d2a3e8ebf8658fb5441ca8a2c233/src/Network/net_ctrls.hxx
"""

from construct import Array, Enum, Const, Padding, Int32ub, Float64b, BitStruct, Flag, BitsInteger, Mapping

RESERVED_SPACE = 25 #: Constant value from define

FG_MAX_ENGINES = 4 #: Constant value from enum
# FG_MAX_WHEELS = 16 #: Constant value from enum. TODO: Unused?
FG_MAX_TANKS = 8 #: Constant value from enum

# Big endian
#: Ctrls v27 structure
ctrls_struct = {
'version': Const(27, Int32ub),
'_padding0': Padding(4), # TODO: Not documented, probably due to struct packing
'aileron': Float64b, # -1 ... 1
'elevator': Float64b, # -1 ... 1
'rudder': Float64b, # -1 ... 1
'aileron_trim': Float64b, # -1 ... 1
'elevator_trim': Float64b, # -1 ... 1
'rudder_trim': Float64b, # -1 ... 1
'flaps': Float64b, # 0 ... 1
'spoilers': Float64b,
'speedbrake': Float64b,
'flaps_power': Enum(Int32ub, unavailable=0, available=1),
'flap_motor_ok': Int32ub,
'num_engines': Int32ub,
'master_bat': Array(FG_MAX_ENGINES, Enum(Int32ub, off=0, on=1)),
'master_alt': Array(FG_MAX_ENGINES, Enum(Int32ub, off=0, on=1)),
'magnetos': Array(FG_MAX_ENGINES, Int32ub),
'starter_power': Array(FG_MAX_ENGINES,
Enum(Int32ub, off=0, on=1)
),
# throttle needs to be moved forward 1 double?
'_padding3': Padding(4), # TODO: Not documented, probably due to struct packing
'throttle': Array(FG_MAX_ENGINES, Float64b), # 0 ... 1
'mixture': Array(FG_MAX_ENGINES, Float64b), # 0 ... 1
'condition': Array(FG_MAX_ENGINES, Float64b), # 0 ... 1
'fuel_pump_power': Array(FG_MAX_ENGINES,
Enum(Int32ub, off=0, on=1)
),
'prop_advance': Array(FG_MAX_ENGINES, Float64b), # 0 ... 1
'feed_tank_to': Array(4, Int32ub),
'reverse': Array(4, Int32ub),
'engine_ok': Array(FG_MAX_ENGINES, Int32ub),
'mag_left_ok': Array(FG_MAX_ENGINES, Int32ub),
'mag_right_ok': Array(FG_MAX_ENGINES, Int32ub),
'spark_plugs_ok': Array(FG_MAX_ENGINES, Enum(Int32ub, fouled=0, ok=1)),
'oil_press_status': Array(FG_MAX_ENGINES,
Enum(Int32ub, normal=0, low=1, full_fail=2)
),
'fuel_pump_ok': Array(FG_MAX_ENGINES, Int32ub),
'num_tanks': Int32ub,
'fuel_selector': Array(FG_MAX_TANKS, Enum(Int32ub, off=0, on=1)),
'xfer_pump': Array(5, Int32ub), # specifies transfer from array value tank to tank specified by int value
'cross_feed': Enum(Int32ub, off=0, on=1),
'_padding4': Padding(4), # TODO: Not documented, probably due to struct packing
'brake_left': Float64b,
'brake_right': Float64b,
'copilot_brake_left': Float64b,
'copilot_brake_right': Float64b,
'brake_parking': Float64b,
'gear_handle': Enum(Int32ub, up=0, down=1),
'master_avionics': Enum(Int32ub, off=0, on=1),
'comm_1_MHz': Float64b, # TODO: Frequencies are all screwed up. Garbage data
'comm_2_MHz': Float64b,
'nav_1_MHz': Float64b,
'nav_2_MHz': Float64b,
'wind_speed_kt': Float64b,
'wind_dir_deg': Float64b,
'turbulence_norm': Float64b,
'temp_c': Float64b,
'press_inhg': Float64b,
'hground_m': Float64b, # ground elevation
'magvar_deg': Float64b, # local magnetic variation
'icing': Int32ub,
'speedup': Int32ub, # integer speedup multiplier
'freeze': BitStruct( # Default is big-endian
other=BitsInteger((Int32ub.sizeof() * 8) - 3), # Rest of uint32 minus the 3 flags
fuel=Flag,
position=Flag,
master=Flag,
),
'_reserved': Padding(Int32ub.length * RESERVED_SPACE),
}
23 changes: 21 additions & 2 deletions flightgear_python/fg_if.py
Expand Up @@ -88,8 +88,9 @@ def _fg_packet_roundtrip(self):
except ConstError as e:
raise FGCommunicationError(f'Could not decode FG stream. Is this the right FDM version?\n{e}') from e

# Fix FG's radian parsing error :(
s = fix_fg_radian_parsing(s)
if isinstance(self, FDMConnection):
# Fix FG's radian parsing error :(
s = fix_fg_radian_parsing(s)

# Call user method
s = self.fg_rx_cb(s, self.event_pipe)
Expand Down Expand Up @@ -144,6 +145,24 @@ def __init__(self, fdm_version: int):
self.fg_net_struct = Struct(*[k / v for k, v in fdm_struct.items()])


class CtrlsConnection(FGConnection):
"""
FlightGear Controls Connection
:param ctrls_version: Net Ctrls version (27)
"""

def __init__(self, ctrls_version: int):
super().__init__()
# TODO: Support auto-version check
if ctrls_version == 27:
from .ctrls_v27 import ctrls_struct
else:
raise NotImplementedError(f'Controls version {ctrls_version} not supported yet')
# Create Struct from Dict
self.fg_net_struct = Struct(*[k / v for k, v in ctrls_struct.items()])


class PropsConnection:
"""
FlightGear Telnet Interface Connection (also known as the property interface).
Expand Down

0 comments on commit 31328ea

Please sign in to comment.