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