[3] | 1 | # Module for generic flight-simulator interfaces
|
---|
| 2 |
|
---|
[4] | 3 | #-------------------------------------------------------------------------------
|
---|
[3] | 4 |
|
---|
[4] | 5 | import const
|
---|
[133] | 6 | from sound import startSound
|
---|
[3] | 7 |
|
---|
[59] | 8 | import fsuipc
|
---|
[133] | 9 | import threading
|
---|
| 10 | import time
|
---|
[59] | 11 |
|
---|
[4] | 12 | #-------------------------------------------------------------------------------
|
---|
[3] | 13 |
|
---|
[4] | 14 | class ConnectionListener(object):
|
---|
[3] | 15 | """Base class for listeners on connections to the flight simulator."""
|
---|
| 16 | def connected(self, fsType, descriptor):
|
---|
| 17 | """Called when a connection has been established to the flight
|
---|
| 18 | simulator of the given type."""
|
---|
| 19 | print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
|
---|
| 20 |
|
---|
[59] | 21 | def connectionFailed(self):
|
---|
| 22 | """Called when the connection could not be established."""
|
---|
| 23 | print "fs.ConnectionListener.connectionFailed"
|
---|
| 24 |
|
---|
[3] | 25 | def disconnected(self):
|
---|
| 26 | """Called when a connection to the flight simulator has been broken."""
|
---|
| 27 | print "fs.ConnectionListener.disconnected"
|
---|
| 28 |
|
---|
[4] | 29 | #-------------------------------------------------------------------------------
|
---|
| 30 |
|
---|
| 31 | class SimulatorException(Exception):
|
---|
| 32 | """Exception thrown by the simulator interface for communication failure."""
|
---|
| 33 |
|
---|
| 34 | #-------------------------------------------------------------------------------
|
---|
| 35 |
|
---|
[15] | 36 | def createSimulator(type, connectionListener):
|
---|
[4] | 37 | """Create a simulator instance for the given simulator type with the given
|
---|
| 38 | connection listener.
|
---|
| 39 |
|
---|
| 40 | The returned object should provide the following members:
|
---|
| 41 | FIXME: add info
|
---|
| 42 | """
|
---|
[15] | 43 | assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
|
---|
| 44 | "Only MS Flight Simulator 2004 and X are supported"
|
---|
[59] | 45 | return fsuipc.Simulator(connectionListener, connectAttempts = 3)
|
---|
[4] | 46 |
|
---|
| 47 | #-------------------------------------------------------------------------------
|
---|
| 48 |
|
---|
[133] | 49 | class MessageThread(threading.Thread):
|
---|
| 50 | """Thread to handle messages."""
|
---|
| 51 | def __init__(self, config, simulator):
|
---|
| 52 | """Initialize the message thread with the given configuration and
|
---|
| 53 | simulator."""
|
---|
| 54 | super(MessageThread, self).__init__()
|
---|
| 55 |
|
---|
| 56 | self._config = config
|
---|
| 57 | self._simulator = simulator
|
---|
| 58 |
|
---|
| 59 | self._requestCondition = threading.Condition()
|
---|
| 60 | self._messages = []
|
---|
| 61 | self._nextMessageTime = None
|
---|
| 62 | self._toQuit = False
|
---|
| 63 |
|
---|
| 64 | self.daemon = True
|
---|
| 65 |
|
---|
[152] | 66 | def add(self, messageType, text, duration, disconnect):
|
---|
[133] | 67 | """Add the given message to the requested messages."""
|
---|
| 68 | with self._requestCondition:
|
---|
[152] | 69 | self._messages.append((messageType, text, duration,
|
---|
| 70 | disconnect))
|
---|
[133] | 71 | self._requestCondition.notify()
|
---|
| 72 |
|
---|
| 73 | def quit(self):
|
---|
| 74 | """Quit the thread."""
|
---|
| 75 | with self._requestCondition:
|
---|
| 76 | self._toQuit = True
|
---|
| 77 | self._requestCondition.notifty()
|
---|
| 78 | self.join()
|
---|
| 79 |
|
---|
| 80 | def run(self):
|
---|
| 81 | """Perform the thread's operation."""
|
---|
| 82 | while True:
|
---|
[152] | 83 | (messageType, text, duration, disconnect) = (None, None, None, None)
|
---|
[133] | 84 | with self._requestCondition:
|
---|
| 85 | now = time.time()
|
---|
| 86 | while not self._toQuit and \
|
---|
| 87 | ((self._nextMessageTime is not None and \
|
---|
| 88 | self._nextMessageTime>now) or \
|
---|
| 89 | not self._messages):
|
---|
| 90 | self._requestCondition.wait(1)
|
---|
| 91 | now = time.time()
|
---|
| 92 |
|
---|
| 93 | if self._toQuit: return
|
---|
| 94 | if self._nextMessageTime is None or \
|
---|
| 95 | self._nextMessageTime<=now:
|
---|
| 96 | self._nextMessageTime = None
|
---|
| 97 |
|
---|
| 98 | if self._messages:
|
---|
[152] | 99 | (messageType, text,
|
---|
| 100 | duration, disconnect) = self._messages[0]
|
---|
[133] | 101 | del self._messages[0]
|
---|
| 102 |
|
---|
| 103 | if text is not None:
|
---|
[152] | 104 | self._sendMessage(messageType, text, duration, disconnect)
|
---|
[133] | 105 |
|
---|
[152] | 106 | def _sendMessage(self, messageType, text, duration, disconnect):
|
---|
[133] | 107 | """Send the message and setup the next message time."""
|
---|
| 108 | messageLevel = self._config.getMessageTypeLevel(messageType)
|
---|
| 109 | if messageLevel==const.MESSAGELEVEL_SOUND or \
|
---|
| 110 | messageLevel==const.MESSAGELEVEL_BOTH:
|
---|
[170] | 111 | startSound(const.SOUND_NOTIFY
|
---|
| 112 | if messageType==const.MESSAGETYPE_VISIBILITY
|
---|
| 113 | else const.SOUND_DING)
|
---|
[133] | 114 | if (messageLevel==const.MESSAGELEVEL_FS or \
|
---|
| 115 | messageLevel==const.MESSAGELEVEL_BOTH):
|
---|
[152] | 116 | if disconnect:
|
---|
| 117 | self._simulator.disconnect("[MLX] " + text,
|
---|
| 118 | duration = duration)
|
---|
| 119 | else:
|
---|
| 120 | self._simulator.sendMessage("[MLX] " + text,
|
---|
| 121 | duration = duration)
|
---|
| 122 | elif disconnecte:
|
---|
| 123 | self._simulator.disconnect()
|
---|
[133] | 124 | self._nextMessageTime = time.time() + duration
|
---|
| 125 |
|
---|
| 126 | #-------------------------------------------------------------------------------
|
---|
| 127 |
|
---|
| 128 | _messageThread = None
|
---|
| 129 |
|
---|
| 130 | #-------------------------------------------------------------------------------
|
---|
| 131 |
|
---|
| 132 | def setupMessageSending(config, simulator):
|
---|
| 133 | """Setup message sending with the given config and simulator."""
|
---|
| 134 | global _messageThread
|
---|
| 135 | if _messageThread is not None:
|
---|
| 136 | _messageThread.quit()
|
---|
| 137 | _messageThread = MessageThread(config, simulator)
|
---|
| 138 | _messageThread.start()
|
---|
| 139 |
|
---|
| 140 | #-------------------------------------------------------------------------------
|
---|
| 141 |
|
---|
[152] | 142 | def sendMessage(messageType, text, duration = 3, disconnect = False):
|
---|
[133] | 143 | """Send the given message of the given type into the simulator and/or play
|
---|
| 144 | a corresponding sound."""
|
---|
| 145 | global _messageThread
|
---|
| 146 | if _messageThread is not None:
|
---|
[152] | 147 | _messageThread.add(messageType, text, duration, disconnect)
|
---|
[133] | 148 |
|
---|
| 149 | #-------------------------------------------------------------------------------
|
---|
| 150 |
|
---|
[4] | 151 | class AircraftState(object):
|
---|
| 152 | """Base class for the aircraft state produced by the aircraft model based
|
---|
| 153 | on readings from the simulator.
|
---|
| 154 |
|
---|
| 155 | The following data members should be provided at least:
|
---|
| 156 | - timestamp: the simulator time of the measurement in seconds since the
|
---|
| 157 | epoch (float)
|
---|
[89] | 158 | - latitude (in degrees, North is positive)
|
---|
| 159 | - longitude (in degrees, East is positive)
|
---|
[4] | 160 | - paused: a boolean indicating if the flight simulator is paused for
|
---|
| 161 | whatever reason (it could be a pause mode, or a menu, a dialog, or a
|
---|
| 162 | replay, etc.)
|
---|
| 163 | - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
|
---|
| 164 | MSFS) is activated
|
---|
| 165 | - overspeed: a boolean indicating if the aircraft is in overspeed
|
---|
| 166 | - stalled: a boolean indicating if the aircraft is stalled
|
---|
[89] | 167 | - onTheGround: a boolean indicating if the aircraft is on the ground
|
---|
[9] | 168 | - zfw: the zero-fuel weight in kilograms (float)
|
---|
[4] | 169 | - grossWeight: the gross weight in kilograms (float)
|
---|
| 170 | - heading: the heading of the aircraft in degrees (float)
|
---|
| 171 | - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
|
---|
| 172 | negative means pitch up (float)
|
---|
| 173 | - bank: the bank of the aircraft in degrees. Positive means bank left,
|
---|
| 174 | negative means bank right (float)
|
---|
[197] | 175 | - ias: the indicated airspeed in knots (float)
|
---|
| 176 | - smoothedIAS: the smoothed IAS in knots (float)
|
---|
[9] | 177 | - mach: the airspeed in mach (float)
|
---|
[5] | 178 | - groundSpeed: the ground speed (float)
|
---|
[4] | 179 | - vs: the vertical speed in feet/minutes (float)
|
---|
[197] | 180 | - smoothedVS: the smoothed VS in feet/minutes (float)
|
---|
[8] | 181 | - radioAltitude: the radio altitude of the aircraft in feet (float)
|
---|
[4] | 182 | - altitude: the altitude of the aircraft in feet (float)
|
---|
[5] | 183 | - gLoad: G-load (float)
|
---|
[4] | 184 | - flapsSet: the selected degrees of the flaps (float)
|
---|
| 185 | - flaps: the actual degrees of the flaps (float)
|
---|
[5] | 186 | - fuelWeight[]: the fuel weights in the different tanks in kgs (array of
|
---|
| 187 | floats of as many items as the number fuel tanks)
|
---|
[4] | 188 | - n1[]: the N1 values of the turbine engines (array of floats
|
---|
| 189 | of as many items as the number of engines, present only for aircraft with
|
---|
| 190 | turbines)
|
---|
| 191 | - rpm[]: the RPM values of the piston engines (array of floats
|
---|
| 192 | of as many items as the number of engines, present only for aircraft with
|
---|
| 193 | pistons)
|
---|
| 194 | - reverser[]: an array of booleans indicating if the thrust reversers are
|
---|
| 195 | activated on any of the engines. The number of items equals to the number
|
---|
[11] | 196 | of engines with a reverser.
|
---|
[4] | 197 | - navLightsOn: a boolean indicating if the navigation lights are on
|
---|
| 198 | - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
|
---|
| 199 | - strobeLightsOn: a boolean indicating if the strobe lights are on
|
---|
[15] | 200 | - landingLightsOn: a boolean indicating if the landing lights are on
|
---|
[4] | 201 | - pitotHeatOn: a boolean indicating if the pitot heat is on
|
---|
[8] | 202 | - parking: a boolean indicating if the parking brake is set
|
---|
[209] | 203 | - gearControlDown: a boolean indicating if the gear control is set to down
|
---|
[4] | 204 | - gearsDown: a boolean indicating if the gears are down
|
---|
| 205 | - spoilersArmed: a boolean indicating if the spoilers have been armed for
|
---|
| 206 | automatic deployment
|
---|
| 207 | - spoilersExtension: the percentage of how much the spoiler is extended
|
---|
| 208 | (float)
|
---|
[5] | 209 | - altimeter: the altimeter setting in hPa (float)
|
---|
| 210 | - nav1: the frequency of the NAV1 radio in MHz (string)
|
---|
| 211 | - nav2: the frequency of the NAV1 radio in MHz (string)
|
---|
[15] | 212 | - squawk: the transponder code (string)
|
---|
[9] | 213 | - windSpeed: the speed of the wind at the aircraft in knots (float)
|
---|
| 214 | - windDirection: the direction of the wind at the aircraft in degrees (float)
|
---|
[134] | 215 | - visibility: the visibility in metres (float)
|
---|
[4] | 216 |
|
---|
| 217 | FIXME: needed when taxiing only:
|
---|
[5] | 218 | - payload weight
|
---|
[4] | 219 |
|
---|
[5] | 220 | FIXME: needed rarely:
|
---|
| 221 | - latitude, longitude
|
---|
| 222 | - transporter
|
---|
| 223 | - visibility
|
---|
[4] | 224 | """
|
---|
| 225 |
|
---|