[298] | 1 |
|
---|
[919] | 2 | from . import const
|
---|
| 3 | from .sound import startSound
|
---|
[298] | 4 |
|
---|
[420] | 5 | import os
|
---|
| 6 |
|
---|
[919] | 7 | from . import fsuipc
|
---|
| 8 | from . import xplane
|
---|
[420] | 9 |
|
---|
[298] | 10 | import threading
|
---|
| 11 | import time
|
---|
| 12 |
|
---|
| 13 | #-------------------------------------------------------------------------------
|
---|
| 14 |
|
---|
[295] | 15 | ## @package mlx.fs
|
---|
| 16 | #
|
---|
| 17 | # The main interface to the flight simulator.
|
---|
| 18 | #
|
---|
| 19 | # The \ref createSimulator function can be used to create an instance of
|
---|
| 20 | # the class that can be used to access the simulator. It expects an instance of
|
---|
| 21 | # the \ref ConnectionListener class, the member functions of which will be
|
---|
| 22 | # called when something happens with the connection to the simulator.
|
---|
| 23 | #
|
---|
| 24 | # The simulator interface is most often used to retrieve the state of the
|
---|
| 25 | # simulated aircraft. Instances of class \ref AircraftState are used for this
|
---|
| 26 | # purpose.
|
---|
[298] | 27 | #
|
---|
| 28 | # This module also contains some definitions for message sending and implements
|
---|
| 29 | # the timing logic itself.
|
---|
[59] | 30 |
|
---|
[4] | 31 | #-------------------------------------------------------------------------------
|
---|
[3] | 32 |
|
---|
[4] | 33 | class ConnectionListener(object):
|
---|
[3] | 34 | """Base class for listeners on connections to the flight simulator."""
|
---|
| 35 | def connected(self, fsType, descriptor):
|
---|
| 36 | """Called when a connection has been established to the flight
|
---|
| 37 | simulator of the given type."""
|
---|
[919] | 38 | print("fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor)
|
---|
[3] | 39 |
|
---|
[59] | 40 | def connectionFailed(self):
|
---|
| 41 | """Called when the connection could not be established."""
|
---|
[919] | 42 | print("fs.ConnectionListener.connectionFailed")
|
---|
[59] | 43 |
|
---|
[3] | 44 | def disconnected(self):
|
---|
| 45 | """Called when a connection to the flight simulator has been broken."""
|
---|
[919] | 46 | print("fs.ConnectionListener.disconnected")
|
---|
[3] | 47 |
|
---|
[4] | 48 | #-------------------------------------------------------------------------------
|
---|
| 49 |
|
---|
| 50 | class SimulatorException(Exception):
|
---|
| 51 | """Exception thrown by the simulator interface for communication failure."""
|
---|
| 52 |
|
---|
| 53 | #-------------------------------------------------------------------------------
|
---|
| 54 |
|
---|
[15] | 55 | def createSimulator(type, connectionListener):
|
---|
[4] | 56 | """Create a simulator instance for the given simulator type with the given
|
---|
| 57 | connection listener.
|
---|
| 58 |
|
---|
| 59 | The returned object should provide the following members:
|
---|
| 60 | FIXME: add info
|
---|
| 61 | """
|
---|
[499] | 62 | if type in [const.SIM_MSFS9, const.SIM_MSFSX]:
|
---|
| 63 | return fsuipc.Simulator(connectionListener, connectAttempts = 3)
|
---|
[909] | 64 | elif type in [const.SIM_XPLANE9, const.SIM_XPLANE10, const.SIM_XPLANE11]:
|
---|
[499] | 65 | return xplane.Simulator(connectionListener, connectAttempts = 3)
|
---|
| 66 | else:
|
---|
[909] | 67 | "Only MS Flight Simulator 2004 and X or X-Plane 9, 10 and 11 are supported"
|
---|
[4] | 68 |
|
---|
| 69 | #-------------------------------------------------------------------------------
|
---|
| 70 |
|
---|
[133] | 71 | class MessageThread(threading.Thread):
|
---|
| 72 | """Thread to handle messages."""
|
---|
| 73 | def __init__(self, config, simulator):
|
---|
| 74 | """Initialize the message thread with the given configuration and
|
---|
| 75 | simulator."""
|
---|
| 76 | super(MessageThread, self).__init__()
|
---|
| 77 |
|
---|
| 78 | self._config = config
|
---|
| 79 | self._simulator = simulator
|
---|
| 80 |
|
---|
| 81 | self._requestCondition = threading.Condition()
|
---|
| 82 | self._messages = []
|
---|
| 83 | self._nextMessageTime = None
|
---|
| 84 | self._toQuit = False
|
---|
| 85 |
|
---|
| 86 | self.daemon = True
|
---|
| 87 |
|
---|
[152] | 88 | def add(self, messageType, text, duration, disconnect):
|
---|
[133] | 89 | """Add the given message to the requested messages."""
|
---|
| 90 | with self._requestCondition:
|
---|
[152] | 91 | self._messages.append((messageType, text, duration,
|
---|
| 92 | disconnect))
|
---|
[133] | 93 | self._requestCondition.notify()
|
---|
| 94 |
|
---|
| 95 | def quit(self):
|
---|
| 96 | """Quit the thread."""
|
---|
| 97 | with self._requestCondition:
|
---|
| 98 | self._toQuit = True
|
---|
[501] | 99 | self._requestCondition.notify()
|
---|
[133] | 100 | self.join()
|
---|
| 101 |
|
---|
| 102 | def run(self):
|
---|
| 103 | """Perform the thread's operation."""
|
---|
| 104 | while True:
|
---|
[152] | 105 | (messageType, text, duration, disconnect) = (None, None, None, None)
|
---|
[133] | 106 | with self._requestCondition:
|
---|
| 107 | now = time.time()
|
---|
| 108 | while not self._toQuit and \
|
---|
| 109 | ((self._nextMessageTime is not None and \
|
---|
| 110 | self._nextMessageTime>now) or \
|
---|
| 111 | not self._messages):
|
---|
| 112 | self._requestCondition.wait(1)
|
---|
| 113 | now = time.time()
|
---|
| 114 |
|
---|
| 115 | if self._toQuit: return
|
---|
| 116 | if self._nextMessageTime is None or \
|
---|
| 117 | self._nextMessageTime<=now:
|
---|
| 118 | self._nextMessageTime = None
|
---|
| 119 |
|
---|
| 120 | if self._messages:
|
---|
[152] | 121 | (messageType, text,
|
---|
| 122 | duration, disconnect) = self._messages[0]
|
---|
[133] | 123 | del self._messages[0]
|
---|
| 124 |
|
---|
[334] | 125 | if text is not None:
|
---|
[152] | 126 | self._sendMessage(messageType, text, duration, disconnect)
|
---|
[133] | 127 |
|
---|
[152] | 128 | def _sendMessage(self, messageType, text, duration, disconnect):
|
---|
[334] | 129 | """Send the message and setup the next message time."""
|
---|
[133] | 130 | messageLevel = self._config.getMessageTypeLevel(messageType)
|
---|
| 131 | if messageLevel==const.MESSAGELEVEL_SOUND or \
|
---|
| 132 | messageLevel==const.MESSAGELEVEL_BOTH:
|
---|
[170] | 133 | startSound(const.SOUND_NOTIFY
|
---|
| 134 | if messageType==const.MESSAGETYPE_VISIBILITY
|
---|
| 135 | else const.SOUND_DING)
|
---|
[133] | 136 | if (messageLevel==const.MESSAGELEVEL_FS or \
|
---|
| 137 | messageLevel==const.MESSAGELEVEL_BOTH):
|
---|
[152] | 138 | if disconnect:
|
---|
| 139 | self._simulator.disconnect("[MLX] " + text,
|
---|
| 140 | duration = duration)
|
---|
| 141 | else:
|
---|
| 142 | self._simulator.sendMessage("[MLX] " + text,
|
---|
| 143 | duration = duration)
|
---|
[315] | 144 | elif disconnect:
|
---|
[152] | 145 | self._simulator.disconnect()
|
---|
[133] | 146 | self._nextMessageTime = time.time() + duration
|
---|
| 147 |
|
---|
| 148 | #-------------------------------------------------------------------------------
|
---|
| 149 |
|
---|
| 150 | _messageThread = None
|
---|
| 151 |
|
---|
| 152 | #-------------------------------------------------------------------------------
|
---|
| 153 |
|
---|
| 154 | def setupMessageSending(config, simulator):
|
---|
| 155 | """Setup message sending with the given config and simulator."""
|
---|
| 156 | global _messageThread
|
---|
| 157 | if _messageThread is not None:
|
---|
| 158 | _messageThread.quit()
|
---|
| 159 | _messageThread = MessageThread(config, simulator)
|
---|
| 160 | _messageThread.start()
|
---|
| 161 |
|
---|
| 162 | #-------------------------------------------------------------------------------
|
---|
| 163 |
|
---|
[152] | 164 | def sendMessage(messageType, text, duration = 3, disconnect = False):
|
---|
[133] | 165 | """Send the given message of the given type into the simulator and/or play
|
---|
| 166 | a corresponding sound."""
|
---|
| 167 | global _messageThread
|
---|
| 168 | if _messageThread is not None:
|
---|
[152] | 169 | _messageThread.add(messageType, text, duration, disconnect)
|
---|
[133] | 170 |
|
---|
| 171 | #-------------------------------------------------------------------------------
|
---|
| 172 |
|
---|
[4] | 173 | class AircraftState(object):
|
---|
| 174 | """Base class for the aircraft state produced by the aircraft model based
|
---|
| 175 | on readings from the simulator.
|
---|
| 176 |
|
---|
| 177 | The following data members should be provided at least:
|
---|
| 178 | - timestamp: the simulator time of the measurement in seconds since the
|
---|
| 179 | epoch (float)
|
---|
[89] | 180 | - latitude (in degrees, North is positive)
|
---|
| 181 | - longitude (in degrees, East is positive)
|
---|
[4] | 182 | - paused: a boolean indicating if the flight simulator is paused for
|
---|
| 183 | whatever reason (it could be a pause mode, or a menu, a dialog, or a
|
---|
| 184 | replay, etc.)
|
---|
| 185 | - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
|
---|
| 186 | MSFS) is activated
|
---|
| 187 | - overspeed: a boolean indicating if the aircraft is in overspeed
|
---|
| 188 | - stalled: a boolean indicating if the aircraft is stalled
|
---|
[334] | 189 | - onTheGround: a boolean indicating if the aircraft is on the ground
|
---|
[9] | 190 | - zfw: the zero-fuel weight in kilograms (float)
|
---|
[4] | 191 | - grossWeight: the gross weight in kilograms (float)
|
---|
| 192 | - heading: the heading of the aircraft in degrees (float)
|
---|
| 193 | - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
|
---|
| 194 | negative means pitch up (float)
|
---|
| 195 | - bank: the bank of the aircraft in degrees. Positive means bank left,
|
---|
| 196 | negative means bank right (float)
|
---|
[197] | 197 | - ias: the indicated airspeed in knots (float)
|
---|
| 198 | - smoothedIAS: the smoothed IAS in knots (float)
|
---|
[334] | 199 | - mach: the airspeed in mach (float)
|
---|
[5] | 200 | - groundSpeed: the ground speed (float)
|
---|
[4] | 201 | - vs: the vertical speed in feet/minutes (float)
|
---|
[197] | 202 | - smoothedVS: the smoothed VS in feet/minutes (float)
|
---|
[8] | 203 | - radioAltitude: the radio altitude of the aircraft in feet (float)
|
---|
[4] | 204 | - altitude: the altitude of the aircraft in feet (float)
|
---|
[5] | 205 | - gLoad: G-load (float)
|
---|
[4] | 206 | - flapsSet: the selected degrees of the flaps (float)
|
---|
| 207 | - flaps: the actual degrees of the flaps (float)
|
---|
[274] | 208 | - fuel[]: the fuel information. It is a list of tuples with items:
|
---|
| 209 | the fuel tank identifier and the amount of fuel in that tank in
|
---|
| 210 | kgs
|
---|
| 211 | - totalFuel: the total amount of fuel in kg
|
---|
[4] | 212 | - n1[]: the N1 values of the turbine engines (array of floats
|
---|
| 213 | of as many items as the number of engines, present only for aircraft with
|
---|
[766] | 214 | turbines, for other aircraft it is None). If the aircraft has turbines, but
|
---|
| 215 | the N1 values cannot be read reliably, the array contains Nones.
|
---|
[4] | 216 | - rpm[]: the RPM values of the piston engines (array of floats
|
---|
| 217 | of as many items as the number of engines, present only for aircraft with
|
---|
[263] | 218 | pistons, for other aircraft it is None)
|
---|
[4] | 219 | - reverser[]: an array of booleans indicating if the thrust reversers are
|
---|
| 220 | activated on any of the engines. The number of items equals to the number
|
---|
[11] | 221 | of engines with a reverser.
|
---|
[341] | 222 | - navLightsOn: a boolean indicating if the navigation lights are on. If
|
---|
| 223 | the detection of the state of the landing lights is unreliable, and should
|
---|
| 224 | not be considered, this is set to None.
|
---|
[4] | 225 | - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
|
---|
| 226 | - strobeLightsOn: a boolean indicating if the strobe lights are on
|
---|
[314] | 227 | - landingLightsOn: a boolean indicating if the landing lights are on. If
|
---|
| 228 | the detection of the state of the landing lights is unreliable, and should
|
---|
| 229 | not be considered, this is set to None.
|
---|
[666] | 230 | - pitotHeatOn: a boolean indicating if the pitot heat is on. Maybe None, if
|
---|
| 231 | the pitot heat setting cannot be read
|
---|
[8] | 232 | - parking: a boolean indicating if the parking brake is set
|
---|
[209] | 233 | - gearControlDown: a boolean indicating if the gear control is set to down
|
---|
[4] | 234 | - gearsDown: a boolean indicating if the gears are down
|
---|
| 235 | - spoilersArmed: a boolean indicating if the spoilers have been armed for
|
---|
| 236 | automatic deployment
|
---|
| 237 | - spoilersExtension: the percentage of how much the spoiler is extended
|
---|
[334] | 238 | (float)
|
---|
[5] | 239 | - altimeter: the altimeter setting in hPa (float)
|
---|
[408] | 240 | - qnh: the QNH in hPa (float)
|
---|
[394] | 241 | - altimeterReliable: a boolean indicating if the altimeter setting is
|
---|
| 242 | reliable
|
---|
[366] | 243 | - ils: the frequency of the ILS radio in MHz (string). Can be None, if
|
---|
| 244 | the frequency is unreliable or meaningless.
|
---|
| 245 | - ols_obs: the OBS setting of the ILS radio in degrees (int). Can be None, if
|
---|
| 246 | the value is unreliable or meaningless.
|
---|
| 247 | - ils_manual: a boolean indicating if the ILS radio is on manual control
|
---|
[314] | 248 | - nav1: the frequency of the NAV1 radio in MHz (string). Can be None, if
|
---|
| 249 | the frequency is unreliable or meaningless.
|
---|
[320] | 250 | - nav1_obs: the OBS setting of the NAV1 radio in degrees (int). Can be None, if
|
---|
| 251 | the value is unreliable or meaningless.
|
---|
[321] | 252 | - nav1_manual: a boolean indicating if the NAV1 radio is on manual control
|
---|
[314] | 253 | - nav2: the frequency of the NAV1 radio in MHz (string). Can be None, if
|
---|
| 254 | the frequency is unreliable or meaningless.
|
---|
[320] | 255 | - nav2_obs: the OBS setting of the NAV2 radio in degrees (int). Can be None, if
|
---|
| 256 | the value is unreliable or meaningless.
|
---|
[321] | 257 | - nav2_manual: a boolean indicating if the NAV2 radio is on manual control
|
---|
[317] | 258 | - adf1: the frequency of the ADF1 radio in kHz (string). Can be None, if
|
---|
| 259 | the frequency is unreliable or meaningless.
|
---|
| 260 | - adf2: the frequency of the ADF2 radio in kHz (string). Can be None, if
|
---|
| 261 | the frequency is unreliable or meaningless.
|
---|
[15] | 262 | - squawk: the transponder code (string)
|
---|
[9] | 263 | - windSpeed: the speed of the wind at the aircraft in knots (float)
|
---|
| 264 | - windDirection: the direction of the wind at the aircraft in degrees (float)
|
---|
[134] | 265 | - visibility: the visibility in metres (float)
|
---|
[334] | 266 | - cog: the centre of gravity
|
---|
| 267 | - xpdrC: a boolean indicating whether the transponder is in C mode, or
|
---|
| 268 | None, if the state cannot be read properly
|
---|
[361] | 269 | - autoXPDR: a boolean indicating whether the transponder is turned on
|
---|
| 270 | automatically if the aircraft becomes airborne
|
---|
[337] | 271 | - apMaster: a boolean indicating whether the autopilot is switched on, or
|
---|
| 272 | None, if the state cannot be read properly
|
---|
| 273 | - apHeadingHold: a boolean indicating whether the autopilot's heading hold
|
---|
| 274 | mode is switched on, or None, if the state cannot be read properly
|
---|
| 275 | - apHeading: the autopilot heading value in degrees (float),
|
---|
| 276 | or None, if the state cannot be read properly
|
---|
| 277 | - apAltitudeHold: a boolean indicating whether the autopilot's altitude hold
|
---|
| 278 | mode is switched on, or None, if the state cannot be read properly
|
---|
| 279 | - apAltitude: the autopilot altitude value in feet (float),
|
---|
| 280 | or None, if the state cannot be read properly
|
---|
[340] | 281 | - elevatorTrim: a float value indicating the deflection of the elevator
|
---|
| 282 | trim in degrees
|
---|
[390] | 283 | - antiIceOn: a boolean value indicating if some anti-ice system is turned
|
---|
| 284 | on. It may be None, if the state of the anti-ice system cannot be read
|
---|
| 285 | reliably
|
---|
[337] | 286 | """
|
---|