source: src/mlx/fsuipc.py@ 535:f42fe0ead68f

Last change on this file since 535:f42fe0ead68f was 535:f42fe0ead68f, checked in by István Váradi <ivaradi@…>, 11 years ago

Implemented watchdog monitoring the FSUIPC and X-Plane connection handlers

File size: 81.8 KB
RevLine 
[4]1
2import fs
3import const
4import util
[140]5import acft
[535]6from watchdog import Watchdog
[4]7
[3]8import threading
9import os
10import time
[4]11import calendar
12import sys
[26]13import codecs
[340]14import math
[3]15
[162]16if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
[3]17 import pyuipc
18else:
[51]19 import pyuipc_sim as pyuipc
[3]20
[4]21#------------------------------------------------------------------------------
22
[298]23## @package mlx.fsuipc
24#
25# The module towards FSUIPC.
26#
27# This module implements the simulator interface to FSUIPC.
28#
29# The \ref Handler class is thread handling the FSUIPC requests. It can be
30# given read, periodic read and write, requests, that are executed
31# asynchronously, and a callback function is called with the result. This class
32# is used internally within the module.
33#
34# The \ref Simulator class is the actual interface to the flight simulator, and
35# an instance of it is returned by \ref mlx.fs.createSimulator. This object can
36# be used to connect to the simulator and disconnect from it, to query various
37# data and to start and stop the monitoring of the data.
38#
39# \ref AircraftModel is the base class of the aircraft models. A "model" is a
40# particular implementation of an aircraft, such as the PMDG Boeing 737NG in
41# Flight Simulator 2004. Since each type and each model has its peculiarities
42# (e.g. the different engine and fuel tank configurations), each aircraft type
43# has a generic model, that should work for most of the time. However, certain
44# models may implement various controls, gauges, etc. differently, and such
45# peculiarites can be handled in a specific subclass of \ref
46# AircraftModel. These subclasses should be registered as special ones, and if
47# the simulator detects that the aircraft's model became known or has changed,
48# it will check these special models before resorting to the generic ones.
49#
50# The models are responsible also for querying certain parameters, such as the
51# fuel tank configuration. While ideally it should be specific to a type only,
52# it is possible that the model contains different tanks, in which case some
53# tricks may be needed. See the \ref DC3Model "DC-3 (Li-2)" aircraft as an
54# example.
55
56#------------------------------------------------------------------------------
57
58## The mapping of tank types to FSUIPC offsets
[140]59_tank2offset = { const.FUELTANK_CENTRE : 0x0b74,
60 const.FUELTANK_LEFT : 0x0b7c,
61 const.FUELTANK_RIGHT : 0x0b94,
62 const.FUELTANK_LEFT_AUX : 0x0b84,
63 const.FUELTANK_RIGHT_AUX : 0x0b9c,
64 const.FUELTANK_LEFT_TIP : 0x0b8c,
65 const.FUELTANK_RIGHT_TIP : 0x0ba4,
66 const.FUELTANK_EXTERNAL1 : 0x1254,
67 const.FUELTANK_EXTERNAL2 : 0x125c,
68 const.FUELTANK_CENTRE2 : 0x1244 }
69
70#------------------------------------------------------------------------------
71
[3]72class Handler(threading.Thread):
73 """The thread to handle the FSUIPC requests."""
[4]74 @staticmethod
[9]75 def fsuipc2VS(data):
76 """Convert the given vertical speed data read from FSUIPC into feet/min."""
77 return data*60.0/const.FEETTOMETRES/256.0
78
79 @staticmethod
80 def fsuipc2radioAltitude(data):
81 """Convert the given radio altitude data read from FSUIPC into feet."""
82 return data/const.FEETTOMETRES/65536.0
83
84 @staticmethod
85 def fsuipc2Degrees(data):
86 """Convert the given data into degrees."""
87 return data * 360.0 / 65536.0 / 65536.0
88
89 @staticmethod
90 def fsuipc2PositiveDegrees(data):
91 """Convert the given data into positive degrees."""
92 degrees = Handler.fsuipc2Degrees(data)
93 if degrees<0.0: degrees += 360.0
94 return degrees
95
96 @staticmethod
97 def fsuipc2IAS(data):
98 """Convert the given data into indicated airspeed."""
99 return data / 128.0
100
101 @staticmethod
[4]102 def _callSafe(fun):
103 """Call the given function and swallow any exceptions."""
104 try:
105 return fun()
106 except Exception, e:
[401]107 print >> sys.stderr, util.utf2unicode(str(e))
[4]108 return None
109
[59]110 # The number of times a read is attempted
[21]111 NUM_READATTEMPTS = 3
112
[59]113 # The number of connection attempts
114 NUM_CONNECTATTEMPTS = 3
115
116 # The interval between successive connect attempts
117 CONNECT_INTERVAL = 0.25
118
[21]119 @staticmethod
120 def _performRead(data, callback, extra, validator):
121 """Perform a read request.
122
123 If there is a validator, that will be called with the return values,
124 and if the values are wrong, the request is retried at most a certain
125 number of times.
126
127 Return True if the request has succeeded, False if validation has
128 failed during all attempts. An exception may also be thrown if there is
129 some lower-level communication problem."""
130 attemptsLeft = Handler.NUM_READATTEMPTS
131 while attemptsLeft>0:
132 values = pyuipc.read(data)
133 if validator is None or \
134 Handler._callSafe(lambda: validator(values, extra)):
135 Handler._callSafe(lambda: callback(values, extra))
136 return True
137 else:
138 attemptsLeft -= 1
139 return False
140
[3]141 class Request(object):
142 """A simple, one-shot request."""
[21]143 def __init__(self, forWrite, data, callback, extra, validator = None):
[3]144 """Construct the request."""
145 self._forWrite = forWrite
146 self._data = data
147 self._callback = callback
148 self._extra = extra
[21]149 self._validator = validator
[330]150
[3]151 def process(self, time):
[21]152 """Process the request.
153
154 Return True if the request has succeeded, False if data validation
155 has failed for a reading request. An exception may also be thrown
[330]156 if there is some lower-level communication problem."""
[3]157 if self._forWrite:
158 pyuipc.write(self._data)
[4]159 Handler._callSafe(lambda: self._callback(True, self._extra))
[21]160 return True
[3]161 else:
[21]162 return Handler._performRead(self._data, self._callback,
163 self._extra, self._validator)
[3]164
[4]165 def fail(self):
166 """Handle the failure of this request."""
167 if self._forWrite:
168 Handler._callSafe(lambda: self._callback(False, self._extra))
169 else:
170 Handler._callSafe(lambda: self._callback(None, self._extra))
171
[3]172 class PeriodicRequest(object):
173 """A periodic request."""
[21]174 def __init__(self, id, period, data, callback, extra, validator):
[3]175 """Construct the periodic request."""
176 self._id = id
177 self._period = period
[290]178 self._nextFire = time.time()
[3]179 self._data = data
180 self._preparedData = None
181 self._callback = callback
182 self._extra = extra
[21]183 self._validator = validator
[330]184
[3]185 @property
186 def id(self):
187 """Get the ID of this periodic request."""
188 return self._id
189
190 @property
191 def nextFire(self):
192 """Get the next firing time."""
193 return self._nextFire
194
195 def process(self, time):
196 """Check if this request should be executed, and if so, do so.
197
[21]198 time is the time at which the request is being executed. If this
199 function is called too early, nothing is done, and True is
200 returned.
[3]201
[21]202 Return True if the request has succeeded, False if data validation
203 has failed. An exception may also be thrown if there is some
204 lower-level communication problem."""
205 if time<self._nextFire:
206 return True
[330]207
[3]208 if self._preparedData is None:
209 self._preparedData = pyuipc.prepare_data(self._data)
210 self._data = None
211
[21]212 isOK = Handler._performRead(self._preparedData, self._callback,
213 self._extra, self._validator)
[3]214
[21]215 if isOK:
216 while self._nextFire <= time:
217 self._nextFire += self._period
[330]218
[21]219 return isOK
[3]220
[4]221 def fail(self):
222 """Handle the failure of this request."""
223 pass
224
[3]225 def __cmp__(self, other):
226 """Compare two periodic requests. They are ordered by their next
227 firing times."""
228 return cmp(self._nextFire, other._nextFire)
229
[59]230 def __init__(self, connectionListener,
231 connectAttempts = -1, connectInterval = 0.2):
[3]232 """Construct the handler with the given connection listener."""
233 threading.Thread.__init__(self)
234
235 self._connectionListener = connectionListener
[59]236 self._connectAttempts = connectAttempts
237 self._connectInterval = connectInterval
[3]238
239 self._requestCondition = threading.Condition()
240 self._connectionRequested = False
[330]241 self._connected = False
[3]242
243 self._requests = []
244 self._nextPeriodicID = 1
245 self._periodicRequests = []
246
[535]247 self._watchdogClient = Watchdog.get().addClient(2.0, "fsuipc.Handler")
248
[3]249 self.daemon = True
250
[21]251 def requestRead(self, data, callback, extra = None, validator = None):
[3]252 """Request the reading of some data.
253
254 data is a list of tuples of the following items:
255 - the offset of the data as an integer
256 - the type letter of the data as a string
257
258 callback is a function that receives two pieces of data:
[4]259 - the values retrieved or None on error
[3]260 - the extra parameter
261
262 It will be called in the handler's thread!
263 """
264 with self._requestCondition:
[21]265 self._requests.append(Handler.Request(False, data, callback, extra,
266 validator))
[3]267 self._requestCondition.notify()
268
269 def requestWrite(self, data, callback, extra = None):
270 """Request the writing of some data.
271
272 data is a list of tuples of the following items:
273 - the offset of the data as an integer
274 - the type letter of the data as a string
275 - the data to write
276
[4]277 callback is a function that receives two pieces of data:
278 - a boolean indicating if writing was successful
279 - the extra data
280 It will be called in the handler's thread!
[3]281 """
282 with self._requestCondition:
[152]283 request = Handler.Request(True, data, callback, extra)
284 #print "fsuipc.Handler.requestWrite", request
285 self._requests.append(request)
[3]286 self._requestCondition.notify()
287
[4]288 @staticmethod
289 def _readWriteCallback(data, extra):
290 """Callback for the read() and write() calls below."""
291 extra.append(data)
292 with extra[0] as condition:
293 condition.notify()
294
[21]295 def requestPeriodicRead(self, period, data, callback, extra = None,
296 validator = None):
[3]297 """Request a periodic read of data.
298
299 period is a floating point number with the period in seconds.
300
301 This function returns an identifier which can be used to cancel the
302 request."""
303 with self._requestCondition:
304 id = self._nextPeriodicID
305 self._nextPeriodicID += 1
[21]306 request = Handler.PeriodicRequest(id, period, data, callback,
307 extra, validator)
[3]308 self._periodicRequests.append(request)
309 self._requestCondition.notify()
310 return id
311
312 def clearPeriodic(self, id):
313 """Clear the periodic request with the given ID."""
314 with self._requestCondition:
315 for i in range(0, len(self._periodicRequests)):
316 if self._periodicRequests[i].id==id:
317 del self._periodicRequests[i]
318 return True
319 return False
320
321 def connect(self):
322 """Initiate the connection to the flight simulator."""
323 with self._requestCondition:
324 if not self._connectionRequested:
325 self._connectionRequested = True
326 self._requestCondition.notify()
[330]327
[3]328 def disconnect(self):
329 """Disconnect from the flight simulator."""
330 with self._requestCondition:
[59]331 self._requests = []
[3]332 if self._connectionRequested:
[330]333 self._connectionRequested = False
[3]334 self._requestCondition.notify()
335
[59]336 def clearRequests(self):
337 """Clear the outstanding one-shot requests."""
338 with self._requestCondition:
339 self._requests = []
340
[3]341 def run(self):
342 """Perform the operation of the thread."""
343 while True:
344 self._waitConnectionRequest()
[330]345
[168]346 if self._connect()>0:
[3]347 self._handleConnection()
348
349 self._disconnect()
[330]350
[3]351 def _waitConnectionRequest(self):
352 """Wait for a connection request to arrive."""
353 with self._requestCondition:
354 while not self._connectionRequested:
355 self._requestCondition.wait()
[330]356
[168]357 def _connect(self, autoReconnection = False, attempts = 0):
[21]358 """Try to connect to the flight simulator via FSUIPC
359
360 Returns True if the connection has been established, False if it was
361 not due to no longer requested.
362 """
[3]363 while self._connectionRequested:
[168]364 if attempts>=self.NUM_CONNECTATTEMPTS:
365 self._connectionRequested = False
366 if autoReconnection:
[330]367 Handler._callSafe(lambda:
[168]368 self._connectionListener.disconnected())
369 else:
[330]370 Handler._callSafe(lambda:
[168]371 self._connectionListener.connectionFailed())
372 return 0
[330]373
[3]374 try:
[59]375 attempts += 1
[16]376 pyuipc.open(pyuipc.SIM_ANY)
[3]377 description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
[330]378 (pyuipc.fsuipc_version, pyuipc.lib_version,
[3]379 pyuipc.fs_version)
[59]380 if not autoReconnection:
[212]381 fsType = const.SIM_MSFSX \
382 if pyuipc.fs_version == pyuipc.SIM_FSX \
383 else const.SIM_MSFS9
[330]384
385 Handler._callSafe(lambda:
[212]386 self._connectionListener.connected(fsType,
[59]387 description))
[21]388 self._connected = True
[168]389 return attempts
[3]390 except Exception, e:
[401]391 print "fsuipc.Handler._connect: connection failed: " + \
392 util.utf2unicode(str(e)) + \
[168]393 " (attempts: %d)" % (attempts,)
[59]394 if attempts<self.NUM_CONNECTATTEMPTS:
395 time.sleep(self.CONNECT_INTERVAL)
[330]396
[3]397 def _handleConnection(self):
398 """Handle a living connection."""
399 with self._requestCondition:
400 while self._connectionRequested:
[21]401 self._processRequests()
402 self._waitRequest()
403
404 def _waitRequest(self):
405 """Wait for the time of the next request.
406
407 Returns also, if the connection is no longer requested.
408
409 Should be called with the request condition lock held."""
410 while self._connectionRequested:
411 timeout = None
412 if self._periodicRequests:
413 self._periodicRequests.sort()
414 timeout = self._periodicRequests[0].nextFire - time.time()
415
[152]416 if self._requests or \
417 (timeout is not None and timeout <= 0.0):
[21]418 return
[330]419
[21]420 self._requestCondition.wait(timeout)
[330]421
[3]422 def _disconnect(self):
423 """Disconnect from the flight simulator."""
[152]424 print "fsuipc.Handler._disconnect"
[21]425 if self._connected:
426 pyuipc.close()
427 self._connected = False
[330]428
[168]429 def _processRequest(self, request, time, attempts):
[330]430 """Process the given request.
[3]431
[21]432 If an exception occurs or invalid data is read too many times, we try
433 to reconnect.
434
435 This function returns only if the request has succeeded, or if a
436 connection is no longer requested.
437
438 This function is called with the request lock held, but is relased
439 whole processing the request and reconnecting."""
[3]440 self._requestCondition.release()
441
[152]442 #print "fsuipc.Handler._processRequest", request
443
[21]444 needReconnect = False
[3]445 try:
[535]446 self._watchdogClient.set()
[21]447 try:
448 if not request.process(time):
449 print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
450 needReconnect = True
451 except Exception as e:
452 print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
[401]453 util.utf2unicode(str(e)) + \
454 "), reconnecting (attempts=%d)." % (attempts,)
[21]455 needReconnect = True
456
457 if needReconnect:
[59]458 with self._requestCondition:
459 self._requests.insert(0, request)
[21]460 self._disconnect()
[168]461 return self._connect(autoReconnection = True, attempts = attempts)
462 else:
463 return 0
[3]464 finally:
[535]465 self._watchdogClient.clear()
[3]466 self._requestCondition.acquire()
[330]467
[3]468 def _processRequests(self):
469 """Process any pending requests.
470
471 Will be called with the request lock held."""
[168]472 attempts = 0
[3]473 while self._connectionRequested and self._periodicRequests:
474 self._periodicRequests.sort()
475 request = self._periodicRequests[0]
[21]476
477 t = time.time()
478
479 if request.nextFire>t:
480 break
[330]481
[168]482 attempts = self._processRequest(request, t, attempts)
[3]483
484 while self._connectionRequested and self._requests:
485 request = self._requests[0]
486 del self._requests[0]
487
[168]488 attempts = self._processRequest(request, None, attempts)
[3]489
490 return self._connectionRequested
[4]491
492#------------------------------------------------------------------------------
493
494class Simulator(object):
495 """The simulator class representing the interface to the flight simulator
496 via FSUIPC."""
[5]497 # The basic data that should be queried all the time once we are connected
[61]498 timeData = [ (0x0240, "H"), # Year
499 (0x023e, "H"), # Number of day in year
500 (0x023b, "b"), # UTC hour
501 (0x023c, "b"), # UTC minute
502 (0x023a, "b") ] # seconds
[330]503
[61]504 normalData = timeData + \
505 [ (0x3d00, -256), # The name of the current aircraft
[133]506 (0x3c00, -256), # The path of the current AIR file
507 (0x1274, "h") ] # Text display mode
[9]508
509 flareData1 = [ (0x023a, "b"), # Seconds of time
510 (0x31e4, "d"), # Radio altitude
511 (0x02c8, "d") ] # Vertical speed
[330]512
[9]513 flareStartData = [ (0x0e90, "H"), # Ambient wind speed
514 (0x0e92, "H"), # Ambient wind direction
515 (0x0e8a, "H") ] # Visibility
[330]516
[9]517 flareData2 = [ (0x023a, "b"), # Seconds of time
518 (0x0366, "H"), # On the ground
519 (0x02c8, "d"), # Vertical speed
520 (0x030c, "d"), # Touch-down rate
521 (0x02bc, "d"), # IAS
522 (0x0578, "d"), # Pitch
523 (0x057c, "d"), # Bank
524 (0x0580, "d") ] # Heading
525
[148]526 TIME_SYNC_INTERVAL = 3.0
527
[61]528 @staticmethod
529 def _getTimestamp(data):
530 """Convert the given data into a timestamp."""
531 timestamp = calendar.timegm(time.struct_time([data[0],
532 1, 1, 0, 0, 0, -1, 1, 0]))
533 timestamp += data[1] * 24 * 3600
534 timestamp += data[2] * 3600
535 timestamp += data[3] * 60
[330]536 timestamp += data[4]
[61]537
538 return timestamp
539
[168]540 @staticmethod
541 def _appendHotkeyData(data, offset, hotkey):
542 """Append the data for the given hotkey to the given array, that is
543 intended to be passed to requestWrite call on the handler."""
544 data.append((offset + 0, "b", ord(hotkey.key)))
545
546 modifiers = 0
547 if hotkey.ctrl: modifiers |= 0x02
548 if hotkey.shift: modifiers |= 0x01
549 data.append((offset + 1, "b", modifiers))
550
551 data.append((offset + 2, "b", 0))
[330]552
553 data.append((offset + 3, "b", 0))
[168]554
[59]555 def __init__(self, connectionListener, connectAttempts = -1,
556 connectInterval = 0.2):
[5]557 """Construct the simulator.
[330]558
[4]559 The aircraft object passed must provide the following members:
560 - type: one of the AIRCRAFT_XXX constants from const.py
561 - modelChanged(aircraftName, modelName): called when the model handling
562 the aircraft has changed.
[9]563 - handleState(aircraftState): handle the given state.
564 - flareStarted(windSpeed, windDirection, visibility, flareStart,
565 flareStartFS): called when the flare has
566 started. windSpeed is in knots, windDirection is in degrees and
567 visibility is in metres. flareStart and flareStartFS are two time
568 values expressed in seconds that can be used to calculate the flare
[330]569 time.
[9]570 - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
571 ias, pitch, bank, heading): called when the flare has
572 finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
573 are the two time values corresponding to the touchdown time. tdRate is
574 the touch-down rate, tdRateCalculatedBySim indicates if the data comes
575 from the simulator or was calculated by the adapter. The other data
576 are self-explanatory and expressed in their 'natural' units."""
[212]577 self._fsType = None
[16]578 self._aircraft = None
[5]579
[168]580 self._handler = Handler(self,
[59]581 connectAttempts = connectAttempts,
582 connectInterval = connectInterval)
[168]583 self._connectionListener = connectionListener
[5]584 self._handler.start()
585
[133]586 self._scroll = False
587
[148]588 self._syncTime = False
589 self._nextSyncTime = -1
[330]590
[5]591 self._normalRequestID = None
592
593 self._monitoringRequested = False
594 self._monitoring = False
[4]595
[5]596 self._aircraftName = None
597 self._aircraftModel = None
598
[9]599 self._flareRequestID = None
600 self._flareRates = []
601 self._flareStart = None
602 self._flareStartFS = None
[152]603
[168]604 self._hotkeyLock = threading.Lock()
605 self._hotkeys = None
606 self._hotkeySetID = 0
607 self._hotkeySetGeneration = 0
608 self._hotkeyOffets = None
609 self._hotkeyRequestID = None
610 self._hotkeyCallback = None
611
[26]612 self._latin1decoder = codecs.getdecoder("iso-8859-1")
[290]613 self._fuelCallback = None
[141]614
[16]615 def connect(self, aircraft):
[5]616 """Initiate a connection to the simulator."""
[16]617 self._aircraft = aircraft
618 self._aircraftName = None
619 self._aircraftModel = None
[5]620 self._handler.connect()
[59]621 if self._normalRequestID is None:
[148]622 self._nextSyncTime = -1
[59]623 self._startDefaultNormal()
624
625 def reconnect(self):
626 """Initiate a reconnection to the simulator.
627
628 It does not reset already set up data, just calls connect() on the
629 handler."""
630 self._handler.connect()
631
632 def requestZFW(self, callback):
633 """Send a request for the ZFW."""
634 self._handler.requestRead([(0x3bfc, "d")], self._handleZFW, extra = callback)
[61]635
[117]636 def requestWeights(self, callback):
637 """Request the following weights: DOW, ZFW, payload.
638
639 These values will be passed to the callback function in this order, as
[330]640 separate arguments."""
[117]641 self._handler.requestRead([(0x13fc, "d")], self._handlePayloadCount,
642 extra = callback)
643
[61]644 def requestTime(self, callback):
645 """Request the time from the simulator."""
646 self._handler.requestRead(Simulator.timeData, self._handleTime,
647 extra = callback)
[330]648
[5]649 def startMonitoring(self):
650 """Start the periodic monitoring of the aircraft and pass the resulting
651 state to the aircraft object periodically."""
[330]652 assert not self._monitoringRequested
[5]653 self._monitoringRequested = True
[4]654
655 def stopMonitoring(self):
656 """Stop the periodic monitoring of the aircraft."""
[330]657 assert self._monitoringRequested
[5]658 self._monitoringRequested = False
[4]659
[9]660 def startFlare(self):
661 """Start monitoring the flare time.
662
663 At present it is assumed to be called from the FSUIPC thread, hence no
664 protection."""
665 #self._aircraft.logger.debug("startFlare")
666 if self._flareRequestID is None:
667 self._flareRates = []
668 self._flareRequestID = self._handler.requestPeriodicRead(0.1,
669 Simulator.flareData1,
670 self._handleFlare1)
671
672 def cancelFlare(self):
673 """Cancel monitoring the flare time.
674
675 At present it is assumed to be called from the FSUIPC thread, hence no
676 protection."""
677 if self._flareRequestID is not None:
678 self._handler.clearPeriodic(self._flareRequestID)
679 self._flareRequestID = None
680
[152]681 def sendMessage(self, message, duration = 3,
682 _disconnect = False):
[133]683 """Send a message to the pilot via the simulator.
684
685 duration is the number of seconds to keep the message displayed."""
[315]686
687 print "fsuipc.Simulator.sendMessage:", message
688
[133]689 if self._scroll:
690 if duration==0: duration = -1
691 elif duration == 1: duration = -2
692 else: duration = -duration
693
694 data = [(0x3380, -1 - len(message), message),
695 (0x32fa, 'h', duration)]
696
[152]697 #if _disconnect:
698 # print "fsuipc.Simulator.sendMessage(disconnect)", message
699
700 self._handler.requestWrite(data, self._handleMessageSent,
701 extra = _disconnect)
[141]702
[274]703 def getFuel(self, callback):
704 """Get the fuel information for the current model.
[141]705
[274]706 The callback will be called with a list of triplets with the following
707 items:
708 - the fuel tank identifier
[141]709 - the current weight of the fuel in the tank (in kgs)
710 - the current total capacity of the tank (in kgs)."""
[274]711 if self._aircraftModel is None:
[290]712 self._fuelCallback = callback
[274]713 else:
714 self._aircraftModel.getFuel(self._handler, callback)
[141]715
716 def setFuelLevel(self, levels):
717 """Set the fuel level to the given ones.
718
719 levels is an array of two-tuples, where each tuple consists of the
720 following:
721 - the const.FUELTANK_XXX constant denoting the tank that must be set,
722 - the requested level of the fuel as a floating-point value between 0.0
723 and 1.0."""
[274]724 if self._aircraftModel is not None:
725 self._aircraftModel.setFuelLevel(self._handler, levels)
[148]726
727 def enableTimeSync(self):
728 """Enable the time synchronization."""
729 self._nextSyncTime = -1
730 self._syncTime = True
[330]731
[148]732 def disableTimeSync(self):
733 """Enable the time synchronization."""
734 self._syncTime = False
735 self._nextSyncTime = -1
[168]736
737 def listenHotkeys(self, hotkeys, callback):
738 """Start listening to the given hotkeys.
739
740 callback is function expecting two arguments:
741 - the ID of the hotkey set as returned by this function,
742 - the list of the indexes of the hotkeys that were pressed."""
743 with self._hotkeyLock:
744 assert self._hotkeys is None
745
746 self._hotkeys = hotkeys
747 self._hotkeySetID += 1
748 self._hotkeySetGeneration = 0
749 self._hotkeyCallback = callback
[330]750
[168]751 self._handler.requestRead([(0x320c, "u")],
752 self._handleNumHotkeys,
753 (self._hotkeySetID,
754 self._hotkeySetGeneration))
755
756 return self._hotkeySetID
757
758 def clearHotkeys(self):
759 """Clear the current hotkey set.
760
761 Note that it is possible, that the callback function set either
762 previously or after calling this function by listenHotkeys() will be
763 called with data from the previous hotkey set.
764
765 Therefore it is recommended to store the hotkey set ID somewhere and
766 check that in the callback function. Right before calling
767 clearHotkeys(), this stored ID should be cleared so that the check
768 fails for sure."""
769 with self._hotkeyLock:
770 if self._hotkeys is not None:
771 self._hotkeys = None
772 self._hotkeySetID += 1
773 self._hotkeyCallback = None
774 self._clearHotkeyRequest()
[330]775
[152]776 def disconnect(self, closingMessage = None, duration = 3):
[4]777 """Disconnect from the simulator."""
[330]778 assert not self._monitoringRequested
[152]779
780 print "fsuipc.Simulator.disconnect", closingMessage, duration
[330]781
[5]782 self._stopNormal()
[168]783 self.clearHotkeys()
[152]784 if closingMessage is None:
785 self._handler.disconnect()
786 else:
787 self.sendMessage(closingMessage, duration = duration,
788 _disconnect = True)
[4]789
[168]790 def connected(self, fsType, descriptor):
791 """Called when a connection has been established to the flight
792 simulator of the given type."""
[212]793 self._fsType = fsType
[168]794 with self._hotkeyLock:
795 if self._hotkeys is not None:
796 self._hotkeySetGeneration += 1
[330]797
[168]798 self._handler.requestRead([(0x320c, "u")],
799 self._handleNumHotkeys,
800 (self._hotkeySetID,
801 self._hotkeySetGeneration))
802 self._connectionListener.connected(fsType, descriptor)
803
804 def connectionFailed(self):
805 """Called when the connection could not be established."""
806 with self._hotkeyLock:
807 self._clearHotkeyRequest()
808 self._connectionListener.connectionFailed()
809
810 def disconnected(self):
811 """Called when a connection to the flight simulator has been broken."""
812 with self._hotkeyLock:
813 self._clearHotkeyRequest()
814 self._connectionListener.disconnected()
815
[5]816 def _startDefaultNormal(self):
817 """Start the default normal periodic request."""
818 assert self._normalRequestID is None
[21]819 self._normalRequestID = \
820 self._handler.requestPeriodicRead(1.0,
821 Simulator.normalData,
822 self._handleNormal,
823 validator = self._validateNormal)
[5]824
825 def _stopNormal(self):
826 """Stop the normal period request."""
827 assert self._normalRequestID is not None
828 self._handler.clearPeriodic(self._normalRequestID)
829 self._normalRequestID = None
[16]830 self._monitoring = False
[5]831
[21]832 def _validateNormal(self, data, extra):
833 """Validate the normal data."""
834 return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
835
[5]836 def _handleNormal(self, data, extra):
837 """Handle the reply to the normal request.
[4]838
[5]839 At the beginning the result consists the data for normalData. When
840 monitoring is started, it contains the result also for the
841 aircraft-specific values.
[4]842 """
[61]843 timestamp = Simulator._getTimestamp(data)
[8]844
845 createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
[290]846 if self._fuelCallback is not None:
847 self._aircraftModel.getFuel(self._handler, self._fuelCallback)
848 self._fuelCallback = None
[133]849
850 self._scroll = data[7]!=0
[148]851
[5]852 if self._monitoringRequested and not self._monitoring:
853 self._stopNormal()
[4]854 self._startMonitoring()
[5]855 elif self._monitoring and not self._monitoringRequested:
856 self._stopNormal()
857 self._startDefaultNormal()
[8]858 elif self._monitoring and self._aircraftModel is not None and \
859 not createdNewModel:
[330]860 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
[8]861 timestamp, data)
[161]862
863 self._checkTimeSync(aircraftState)
[330]864
[5]865 self._aircraft.handleState(aircraftState)
[4]866
[161]867 def _checkTimeSync(self, aircraftState):
[148]868 """Check if we need to synchronize the FS time."""
[161]869 if not self._syncTime or aircraftState.paused or \
870 self._flareRequestID is not None:
871 self._nextSyncTime = -1
872 return
[148]873
874 now = time.time()
875 seconds = time.gmtime(now).tm_sec
876
877 if seconds>30 and seconds<59:
878 if self._nextSyncTime > (now - 0.49):
879 return
[330]880
[148]881 self._handler.requestWrite([(0x023a, "b", int(seconds))],
882 self._handleTimeSynced)
[330]883
[148]884 #print "Set the seconds to ", seconds
885
886 if self._nextSyncTime<0:
887 self._nextSyncTime = now
[330]888
[148]889 self._nextSyncTime += Simulator.TIME_SYNC_INTERVAL
890 else:
891 self._nextSyncTime = -1
892
893 def _handleTimeSynced(self, success, extra):
894 """Callback for the time sync result."""
895 pass
[330]896
[8]897 def _setAircraftName(self, timestamp, name, airPath):
[4]898 """Set the name of the aicraft and if it is different from the
899 previous, create a new model for it.
[330]900
[8]901 If so, also notifty the aircraft about the change.
902
903 Return if a new model was created."""
[401]904 name = self._latin1decoder(name)[0]
905 airPath = self._latin1decoder(airPath)[0]
906
[8]907 aircraftName = (name, airPath)
908 if aircraftName==self._aircraftName:
909 return False
[4]910
[314]911 print "fsuipc.Simulator: new aircraft name and air file path: %s, %s" % \
912 (name, airPath)
913
[8]914 self._aircraftName = aircraftName
915 needNew = self._aircraftModel is None
916 needNew = needNew or\
917 not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
918 if not needNew:
919 specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
920 needNew = specialModel is not None and \
921 specialModel is not self._aircraftModel.__class__
922
923 if needNew:
[401]924 self._setAircraftModel(AircraftModel.create(self._aircraft,
925 aircraftName))
[330]926
[401]927 self._aircraft.modelChanged(timestamp, name, self._aircraftModel.name)
[8]928
929 return needNew
[4]930
931 def _setAircraftModel(self, model):
932 """Set a new aircraft model.
933
934 It will be queried for the data to monitor and the monitoring request
935 will be replaced by a new one."""
936 self._aircraftModel = model
[330]937
[5]938 if self._monitoring:
[8]939 self._stopNormal()
[5]940 self._startMonitoring()
[330]941
[5]942 def _startMonitoring(self):
943 """Start monitoring with the current aircraft model."""
944 data = Simulator.normalData[:]
[212]945 self._aircraftModel.addMonitoringData(data, self._fsType)
[330]946
[5]947 self._normalRequestID = \
[330]948 self._handler.requestPeriodicRead(1.0, data,
[21]949 self._handleNormal,
950 validator = self._validateNormal)
[16]951 self._monitoring = True
[4]952
[9]953 def _addFlareRate(self, data):
954 """Append a flare rate to the list of last rates."""
955 if len(self._flareRates)>=3:
956 del self._flareRates[0]
957 self._flareRates.append(Handler.fsuipc2VS(data))
958
959 def _handleFlare1(self, data, normal):
960 """Handle the first stage of flare monitoring."""
961 #self._aircraft.logger.debug("handleFlare1: " + str(data))
962 if Handler.fsuipc2radioAltitude(data[1])<=50.0:
963 self._flareStart = time.time()
964 self._flareStartFS = data[0]
965 self._handler.clearPeriodic(self._flareRequestID)
966 self._flareRequestID = \
[330]967 self._handler.requestPeriodicRead(0.1,
[9]968 Simulator.flareData2,
969 self._handleFlare2)
970 self._handler.requestRead(Simulator.flareStartData,
971 self._handleFlareStart)
[330]972
[9]973 self._addFlareRate(data[2])
974
975 def _handleFlareStart(self, data, extra):
976 """Handle the data need to notify the aircraft about the starting of
977 the flare."""
978 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
979 if data is not None:
980 windDirection = data[1]*360.0/65536.0
981 if windDirection<0.0: windDirection += 360.0
982 self._aircraft.flareStarted(data[0], windDirection,
983 data[2]*1609.344/100.0,
984 self._flareStart, self._flareStartFS)
985
986 def _handleFlare2(self, data, normal):
987 """Handle the first stage of flare monitoring."""
988 #self._aircraft.logger.debug("handleFlare2: " + str(data))
989 if data[1]!=0:
990 flareEnd = time.time()
991 self._handler.clearPeriodic(self._flareRequestID)
992 self._flareRequestID = None
993
994 flareEndFS = data[0]
995 if flareEndFS<self._flareStartFS:
996 flareEndFS += 60
997
998 tdRate = Handler.fsuipc2VS(data[3])
999 tdRateCalculatedByFS = True
1000 if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
1001 tdRate = min(self._flareRates)
1002 tdRateCalculatedByFS = False
1003
1004 self._aircraft.flareFinished(flareEnd, flareEndFS,
1005 tdRate, tdRateCalculatedByFS,
1006 Handler.fsuipc2IAS(data[4]),
1007 Handler.fsuipc2Degrees(data[5]),
1008 Handler.fsuipc2Degrees(data[6]),
1009 Handler.fsuipc2PositiveDegrees(data[7]))
1010 else:
1011 self._addFlareRate(data[2])
[59]1012
1013 def _handleZFW(self, data, callback):
1014 """Callback for a ZFW retrieval request."""
1015 zfw = data[0] * const.LBSTOKG / 256.0
1016 callback(zfw)
[330]1017
[61]1018 def _handleTime(self, data, callback):
1019 """Callback for a time retrieval request."""
1020 callback(Simulator._getTimestamp(data))
[117]1021
1022 def _handlePayloadCount(self, data, callback):
1023 """Callback for the payload count retrieval request."""
1024 payloadCount = data[0]
1025 data = [(0x3bfc, "d"), (0x30c0, "f")]
1026 for i in range(0, payloadCount):
1027 data.append((0x1400 + i*48, "f"))
[330]1028
[117]1029 self._handler.requestRead(data, self._handleWeights,
1030 extra = callback)
1031
1032 def _handleWeights(self, data, callback):
1033 """Callback for the weights retrieval request."""
1034 zfw = data[0] * const.LBSTOKG / 256.0
1035 grossWeight = data[1] * const.LBSTOKG
1036 payload = sum(data[2:]) * const.LBSTOKG
1037 dow = zfw - payload
1038 callback(dow, payload, zfw, grossWeight)
[133]1039
[152]1040 def _handleMessageSent(self, success, disconnect):
[133]1041 """Callback for a message sending request."""
[152]1042 #print "fsuipc.Simulator._handleMessageSent", disconnect
1043 if disconnect:
1044 self._handler.disconnect()
[141]1045
[168]1046 def _handleNumHotkeys(self, data, (id, generation)):
1047 """Handle the result of the query of the number of hotkeys"""
1048 with self._hotkeyLock:
1049 if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
1050 numHotkeys = data[0]
1051 print "fsuipc.Simulator._handleNumHotkeys: numHotkeys:", numHotkeys
[330]1052 data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]
[168]1053 self._handler.requestRead(data, self._handleHotkeyTable,
1054 (id, generation))
1055
1056 def _setupHotkeys(self, data):
1057 """Setup the hiven hotkeys and return the data to be written.
1058
1059 If there were hotkeys set previously, they are reused as much as
1060 possible. Any of them not reused will be cleared."""
1061 hotkeys = self._hotkeys
1062 numHotkeys = len(hotkeys)
1063
1064 oldHotkeyOffsets = set([] if self._hotkeyOffets is None else
1065 self._hotkeyOffets)
1066
1067 self._hotkeyOffets = []
1068 numOffsets = 0
1069
1070 while oldHotkeyOffsets:
1071 offset = oldHotkeyOffsets.pop()
1072 self._hotkeyOffets.append(offset)
1073 numOffsets += 1
1074
1075 if numOffsets>=numHotkeys:
1076 break
1077
1078 for i in range(0, len(data)):
1079 if numOffsets>=numHotkeys:
1080 break
1081
1082 if data[i]==0:
1083 self._hotkeyOffets.append(0x3210 + i*4)
1084 numOffsets += 1
1085
1086 writeData = []
1087 for i in range(0, numOffsets):
1088 Simulator._appendHotkeyData(writeData,
1089 self._hotkeyOffets[i],
1090 hotkeys[i])
1091
1092 for offset in oldHotkeyOffsets:
1093 writeData.append((offset, "u", long(0)))
1094
1095 return writeData
1096
1097 def _handleHotkeyTable(self, data, (id, generation)):
1098 """Handle the result of the query of the hotkey table."""
1099 with self._hotkeyLock:
1100 if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
1101 writeData = self._setupHotkeys(data)
1102 self._handler.requestWrite(writeData,
1103 self._handleHotkeysWritten,
1104 (id, generation))
[330]1105
[168]1106 def _handleHotkeysWritten(self, success, (id, generation)):
1107 """Handle the result of the hotkeys having been written."""
[330]1108 with self._hotkeyLock:
[168]1109 if success and id==self._hotkeySetID and \
1110 generation==self._hotkeySetGeneration:
1111 data = [(offset + 3, "b") for offset in self._hotkeyOffets]
[330]1112
[168]1113 self._hotkeyRequestID = \
1114 self._handler.requestPeriodicRead(0.5, data,
1115 self._handleHotkeys,
1116 (id, generation))
1117
1118 def _handleHotkeys(self, data, (id, generation)):
[330]1119 """Handle the hotkeys."""
[168]1120 with self._hotkeyLock:
1121 if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
1122 return
1123
1124 callback = self._hotkeyCallback
1125 offsets = self._hotkeyOffets
1126
1127 hotkeysPressed = []
1128 for i in range(0, len(data)):
1129 if data[i]!=0:
1130 hotkeysPressed.append(i)
1131
1132 if hotkeysPressed:
1133 data = []
1134 for index in hotkeysPressed:
1135 data.append((offsets[index]+3, "b", int(0)))
1136 self._handler.requestWrite(data, self._handleHotkeysCleared)
1137
1138 callback(id, hotkeysPressed)
1139
1140 def _handleHotkeysCleared(self, sucess, extra):
[330]1141 """Callback for the hotkey-clearing write request."""
[168]1142
1143 def _clearHotkeyRequest(self):
1144 """Clear the hotkey request in the handler if there is any."""
1145 if self._hotkeyRequestID is not None:
1146 self._handler.clearPeriodic(self._hotkeyRequestID)
1147 self._hotkeyRequestID = None
[330]1148
[4]1149#------------------------------------------------------------------------------
1150
1151class AircraftModel(object):
1152 """Base class for the aircraft models.
1153
1154 Aircraft models handle the data arriving from FSUIPC and turn it into an
1155 object describing the aircraft's state."""
[8]1156 monitoringData = [("paused", 0x0264, "H"),
[89]1157 ("latitude", 0x0560, "l"),
1158 ("longitude", 0x0568, "l"),
[4]1159 ("frozen", 0x3364, "H"),
[5]1160 ("replay", 0x0628, "d"),
[4]1161 ("slew", 0x05dc, "H"),
1162 ("overspeed", 0x036d, "b"),
1163 ("stalled", 0x036c, "b"),
1164 ("onTheGround", 0x0366, "H"),
[9]1165 ("zfw", 0x3bfc, "d"),
[4]1166 ("grossWeight", 0x30c0, "f"),
1167 ("heading", 0x0580, "d"),
1168 ("pitch", 0x0578, "d"),
1169 ("bank", 0x057c, "d"),
1170 ("ias", 0x02bc, "d"),
[9]1171 ("mach", 0x11c6, "H"),
[5]1172 ("groundSpeed", 0x02b4, "d"),
[4]1173 ("vs", 0x02c8, "d"),
[8]1174 ("radioAltitude", 0x31e4, "d"),
[4]1175 ("altitude", 0x0570, "l"),
[5]1176 ("gLoad", 0x11ba, "H"),
[4]1177 ("flapsControl", 0x0bdc, "d"),
1178 ("flapsLeft", 0x0be0, "d"),
1179 ("flapsRight", 0x0be4, "d"),
1180 ("lights", 0x0d0c, "H"),
1181 ("pitot", 0x029c, "b"),
[8]1182 ("parking", 0x0bc8, "H"),
[209]1183 ("gearControl", 0x0be8, "d"),
[4]1184 ("noseGear", 0x0bec, "d"),
1185 ("spoilersArmed", 0x0bcc, "d"),
[5]1186 ("spoilers", 0x0bd0, "d"),
1187 ("altimeter", 0x0330, "H"),
[408]1188 ("qnh", 0x0ec6, "H"),
[5]1189 ("nav1", 0x0350, "H"),
[320]1190 ("nav1_obs", 0x0c4e, "H"),
[8]1191 ("nav2", 0x0352, "H"),
[321]1192 ("nav2_obs", 0x0c5e, "H"),
[317]1193 ("adf1_main", 0x034c, "H"),
1194 ("adf1_ext", 0x0356, "H"),
1195 ("adf2_main", 0x02d4, "H"),
1196 ("adf2_ext", 0x02d6, "H"),
[9]1197 ("squawk", 0x0354, "H"),
1198 ("windSpeed", 0x0e90, "H"),
[134]1199 ("windDirection", 0x0e92, "H"),
[243]1200 ("visibility", 0x0e8a, "H"),
[334]1201 ("cog", 0x2ef8, "f"),
[337]1202 ("xpdrC", 0x7b91, "b"),
1203 ("apMaster", 0x07bc, "d"),
1204 ("apHeadingHold", 0x07c8, "d"),
1205 ("apHeading", 0x07cc, "H"),
1206 ("apAltitudeHold", 0x07d0, "d"),
[340]1207 ("apAltitude", 0x07d4, "u"),
[390]1208 ("elevatorTrim", 0x2ea0, "f"),
1209 ("eng1DeIce", 0x08b2, "H"),
1210 ("eng2DeIce", 0x094a, "H"),
1211 ("propDeIce", 0x337c, "b"),
1212 ("structDeIce", 0x337d, "b")]
[7]1213
1214 specialModels = []
1215
1216 @staticmethod
1217 def registerSpecial(clazz):
1218 """Register the given class as a special model."""
1219 AircraftModel.specialModels.append(clazz)
1220
[4]1221 @staticmethod
[8]1222 def findSpecial(aircraft, aircraftName):
1223 for specialModel in AircraftModel.specialModels:
1224 if specialModel.doesHandle(aircraft, aircraftName):
1225 return specialModel
1226 return None
1227
1228 @staticmethod
[4]1229 def create(aircraft, aircraftName):
1230 """Create the model for the given aircraft name, and notify the
[7]1231 aircraft about it."""
[8]1232 specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
1233 if specialModel is not None:
1234 return specialModel()
1235 if aircraft.type in _genericModels:
1236 return _genericModels[aircraft.type]()
[7]1237 else:
1238 return GenericModel()
[4]1239
[5]1240 @staticmethod
[8]1241 def convertBCD(data, length):
1242 """Convert a data item encoded as BCD into a string of the given number
1243 of digits."""
1244 bcd = ""
1245 for i in range(0, length):
1246 digit = chr(ord('0') + (data&0x0f))
1247 data >>= 4
1248 bcd = digit + bcd
1249 return bcd
1250
1251 @staticmethod
[5]1252 def convertFrequency(data):
1253 """Convert the given frequency data to a string."""
[8]1254 bcd = AircraftModel.convertBCD(data, 4)
1255 return "1" + bcd[0:2] + "." + bcd[2:4]
[5]1256
[317]1257 @staticmethod
1258 def convertADFFrequency(main, ext):
1259 """Convert the given ADF frequency data to a string."""
1260 mainBCD = AircraftModel.convertBCD(main, 4)
1261 extBCD = AircraftModel.convertBCD(ext, 4)
1262
1263 return (extBCD[1] if extBCD[1]!="0" else "") + \
1264 mainBCD[1:] + "." + extBCD[3]
[330]1265
[4]1266 def __init__(self, flapsNotches):
1267 """Construct the aircraft model.
[330]1268
[4]1269 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1270 self._flapsNotches = flapsNotches
[507]1271 self._xpdrReliable = False
[330]1272
[4]1273 @property
1274 def name(self):
1275 """Get the name for this aircraft model."""
1276 return "FSUIPC/Generic"
[330]1277
[8]1278 def doesHandle(self, aircraft, aircraftName):
[4]1279 """Determine if the model handles the given aircraft name.
[330]1280
[7]1281 This default implementation returns False."""
1282 return False
[4]1283
[7]1284 def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
1285 """Add the given FSUIPC offset and type to the given array and a member
[330]1286 attribute with the given name."""
[7]1287 dest.append((offset, type))
1288 if attrName is not None:
[8]1289 setattr(self, attrName, len(dest)-1)
[7]1290
1291 def _addDataWithIndexMembers(self, dest, prefix, data):
[4]1292 """Add FSUIPC data to the given array and also corresponding index
1293 member variables with the given prefix.
1294
1295 data is a list of triplets of the following items:
1296 - the name of the data item. The index member variable will have a name
1297 created by prepending the given prefix to this name.
1298 - the FSUIPC offset
1299 - the FSUIPC type
[330]1300
[4]1301 The latter two items will be appended to dest."""
1302 for (name, offset, type) in data:
[7]1303 self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
[330]1304
[212]1305 def addMonitoringData(self, data, fsType):
[7]1306 """Add the model-specific monitoring data to the given array."""
[8]1307 self._addDataWithIndexMembers(data, "_monidx_",
1308 AircraftModel.monitoringData)
[330]1309
[8]1310 def getAircraftState(self, aircraft, timestamp, data):
[4]1311 """Get an aircraft state object for the given monitoring data."""
1312 state = fs.AircraftState()
[330]1313
[4]1314 state.timestamp = timestamp
[89]1315
1316 state.latitude = data[self._monidx_latitude] * \
1317 90.0 / 10001750.0 / 65536.0 / 65536.0
1318
1319 state.longitude = data[self._monidx_longitude] * \
1320 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
1321 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
[330]1322
[4]1323 state.paused = data[self._monidx_paused]!=0 or \
[5]1324 data[self._monidx_frozen]!=0 or \
1325 data[self._monidx_replay]!=0
[4]1326 state.trickMode = data[self._monidx_slew]!=0
1327
1328 state.overspeed = data[self._monidx_overspeed]!=0
1329 state.stalled = data[self._monidx_stalled]!=0
1330 state.onTheGround = data[self._monidx_onTheGround]!=0
1331
[9]1332 state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
[6]1333 state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
[330]1334
[9]1335 state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
[4]1336
[9]1337 state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
1338 state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
[4]1339
[9]1340 state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
1341 state.mach = data[self._monidx_mach] / 20480.0
[5]1342 state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
[9]1343 state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
[4]1344
[9]1345 state.radioAltitude = \
1346 Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
[6]1347 state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
[5]1348
1349 state.gLoad = data[self._monidx_gLoad] / 625.0
[330]1350
[4]1351 numNotchesM1 = len(self._flapsNotches) - 1
1352 flapsIncrement = 16383 / numNotchesM1
1353 flapsControl = data[self._monidx_flapsControl]
1354 flapsIndex = flapsControl / flapsIncrement
1355 if flapsIndex < numNotchesM1:
1356 if (flapsControl - (flapsIndex*flapsIncrement) >
1357 (flapsIndex+1)*flapsIncrement - flapsControl):
1358 flapsIndex += 1
1359 state.flapsSet = self._flapsNotches[flapsIndex]
[330]1360
[4]1361 flapsLeft = data[self._monidx_flapsLeft]
[16]1362 state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
[330]1363
[4]1364 lights = data[self._monidx_lights]
[330]1365
[5]1366 state.navLightsOn = (lights&0x01) != 0
1367 state.antiCollisionLightsOn = (lights&0x02) != 0
1368 state.landingLightsOn = (lights&0x04) != 0
1369 state.strobeLightsOn = (lights&0x10) != 0
[330]1370
[4]1371 state.pitotHeatOn = data[self._monidx_pitot]!=0
1372
[8]1373 state.parking = data[self._monidx_parking]!=0
1374
[209]1375 state.gearControlDown = data[self._monidx_gearControl]==16383
[4]1376 state.gearsDown = data[self._monidx_noseGear]==16383
1377
1378 state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
[330]1379
[4]1380 spoilers = data[self._monidx_spoilers]
1381 if spoilers<=4800:
1382 state.spoilersExtension = 0.0
1383 else:
1384 state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
[5]1385
1386 state.altimeter = data[self._monidx_altimeter] / 16.0
[394]1387 state.altimeterReliable = True
[408]1388 state.qnh = data[self._monidx_qnh] / 16.0
[330]1389
[366]1390 state.ils = None
1391 state.ils_obs = None
1392 state.ils_manual = False
[5]1393 state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
[320]1394 state.nav1_obs = data[self._monidx_nav1_obs]
[321]1395 state.nav1_manual = True
[5]1396 state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
[320]1397 state.nav2_obs = data[self._monidx_nav2_obs]
[321]1398 state.nav2_manual = True
[317]1399 state.adf1 = \
1400 AircraftModel.convertADFFrequency(data[self._monidx_adf1_main],
1401 data[self._monidx_adf1_ext])
1402 state.adf2 = \
1403 AircraftModel.convertADFFrequency(data[self._monidx_adf2_main],
1404 data[self._monidx_adf2_ext])
[366]1405
[8]1406 state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
[9]1407
1408 state.windSpeed = data[self._monidx_windSpeed]
1409 state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
1410 if state.windDirection<0.0: state.windDirection += 360.0
[134]1411
1412 state.visibility = data[self._monidx_visibility]*1609.344/100.0
[330]1413
[243]1414 state.cog = data[self._monidx_cog]
[330]1415
[507]1416 if not self._xpdrReliable:
1417 self._xpdrReliable = data[self._monidx_xpdrC]!=0
1418
1419 state.xpdrC = data[self._monidx_xpdrC]!=1 \
1420 if self._xpdrReliable else None
[361]1421 state.autoXPDR = False
[334]1422
[337]1423 state.apMaster = data[self._monidx_apMaster]!=0
1424 state.apHeadingHold = data[self._monidx_apHeadingHold]!=0
1425 state.apHeading = data[self._monidx_apHeading] * 360.0 / 65536.0
1426 state.apAltitudeHold = data[self._monidx_apAltitudeHold]!=0
1427 state.apAltitude = data[self._monidx_apAltitude] / \
1428 const.FEETTOMETRES / 65536.0
1429
[340]1430
1431 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1432
[390]1433 state.antiIceOn = data[self._monidx_eng1DeIce]!=0 or \
1434 data[self._monidx_eng2DeIce]!=0 or \
1435 data[self._monidx_propDeIce]!=0 or \
1436 data[self._monidx_structDeIce]!=0
1437
[4]1438 return state
[5]1439
1440#------------------------------------------------------------------------------
1441
[7]1442class GenericAircraftModel(AircraftModel):
1443 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1444 values and some other common parameters in a generic way."""
[330]1445
[140]1446 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
[7]1447 """Construct the generic aircraft model with the given data.
1448
1449 flapsNotches is an array of how much degrees the individual flaps
1450 notches mean.
1451
[140]1452 fuelTanks is an array of const.FUELTANK_XXX constants about the
1453 aircraft's fuel tanks. They will be converted to offsets.
[7]1454
1455 numEngines is the number of engines the aircraft has.
1456
1457 isN1 determines if the engines have an N1 value or an RPM value
1458 (e.g. pistons)."""
1459 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1460
[140]1461 self._fuelTanks = fuelTanks
[7]1462 self._fuelStartIndex = None
1463 self._numEngines = numEngines
1464 self._engineStartIndex = None
1465 self._isN1 = isN1
1466
[8]1467 def doesHandle(self, aircraft, aircraftName):
1468 """Determine if the model handles the given aircraft name.
[330]1469
[8]1470 This implementation returns True."""
1471 return True
1472
[212]1473 def addMonitoringData(self, data, fsType):
[7]1474 """Add the model-specific monitoring data to the given array."""
[212]1475 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
[330]1476
[274]1477 self._fuelStartIndex = self._addFuelOffsets(data, "_monidx_fuelWeight")
[7]1478
[263]1479 self._engineStartIndex = len(data)
1480 for i in range(0, self._numEngines):
1481 self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h") # throttle lever
1482 if self._isN1:
[23]1483 self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f") # N1
[263]1484 else:
1485 self._addOffsetWithIndexMember(data, 0x0898 + i * 0x98, "H") # RPM
1486 self._addOffsetWithIndexMember(data, 0x08c8 + i * 0x98, "H") # RPM scaler
[330]1487
[8]1488 def getAircraftState(self, aircraft, timestamp, data):
[7]1489 """Get the aircraft state.
1490
[330]1491 Get it from the parent, and then add the data about the fuel levels and
[7]1492 the engine parameters."""
1493 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
[8]1494 timestamp,
[7]1495 data)
1496
[274]1497 (state.fuel, state.totalFuel) = \
1498 self._convertFuelData(data, index = self._monidx_fuelWeight)
[263]1499
1500 state.n1 = [] if self._isN1 else None
1501 state.rpm = None if self._isN1 else []
1502 itemsPerEngine = 2 if self._isN1 else 3
[330]1503
[7]1504 state.reverser = []
1505 for i in range(self._engineStartIndex,
[263]1506 self._engineStartIndex +
1507 itemsPerEngine*self._numEngines,
1508 itemsPerEngine):
1509 state.reverser.append(data[i]<0)
1510 if self._isN1:
1511 state.n1.append(data[i+1])
1512 else:
1513 state.rpm.append(data[i+1] * data[i+2]/65536.0)
[7]1514
1515 return state
1516
[274]1517 def getFuel(self, handler, callback):
1518 """Get the fuel information for this model.
1519
1520 See Simulator.getFuel for more information. This
1521 implementation simply queries the fuel tanks given to the
1522 constructor."""
1523 data = []
1524 self._addFuelOffsets(data)
1525
1526 handler.requestRead(data, self._handleFuelRetrieved,
1527 extra = callback)
1528
1529 def setFuelLevel(self, handler, levels):
1530 """Set the fuel level.
1531
1532 See the description of Simulator.setFuelLevel. This
1533 implementation simply sets the fuel tanks as given."""
1534 data = []
1535 for (tank, level) in levels:
1536 offset = _tank2offset[tank]
1537 value = long(level * 128.0 * 65536.0)
1538 data.append( (offset, "u", value) )
1539
1540 handler.requestWrite(data, self._handleFuelWritten)
1541
1542 def _addFuelOffsets(self, data, weightIndexName = None):
1543 """Add the fuel offsets to the given data array.
1544
1545 If weightIndexName is not None, it will be the name of the
1546 fuel weight index.
1547
1548 Returns the index of the first fuel tank's data."""
1549 self._addOffsetWithIndexMember(data, 0x0af4, "H", weightIndexName)
1550
1551 fuelStartIndex = len(data)
1552 for tank in self._fuelTanks:
1553 offset = _tank2offset[tank]
1554 self._addOffsetWithIndexMember(data, offset, "u") # tank level
1555 self._addOffsetWithIndexMember(data, offset+4, "u") # tank capacity
[330]1556
[274]1557 return fuelStartIndex
1558
1559 def _convertFuelData(self, data, index = 0, addCapacities = False):
1560 """Convert the given data into a fuel info list.
1561
1562 The list consists of two or three-tuples of the following
1563 items:
1564 - the fuel tank ID,
1565 - the amount of the fuel in kg,
1566 - if addCapacities is True, the total capacity of the tank."""
1567 fuelWeight = data[index] / 256.0
1568 index += 1
1569
1570 result = []
1571 totalFuel = 0
1572 for fuelTank in self._fuelTanks:
1573 capacity = data[index+1] * fuelWeight * const.LBSTOKG
[406]1574 if capacity>=1.0:
1575 amount = data[index] * capacity / 128.0 / 65536.0
[330]1576
[406]1577 result.append( (fuelTank, amount, capacity) if addCapacities
1578 else (fuelTank, amount))
1579 totalFuel += amount
[412]1580 index += 2
[274]1581
[330]1582 return (result, totalFuel)
[274]1583
1584 def _handleFuelRetrieved(self, data, callback):
1585 """Callback for a fuel retrieval request."""
[330]1586 (fuelData, _totalFuel) = self._convertFuelData(data,
[274]1587 addCapacities = True)
1588 callback(fuelData)
[330]1589
[274]1590 def _handleFuelWritten(self, success, extra):
1591 """Callback for a fuel setting request."""
1592 pass
1593
[7]1594#------------------------------------------------------------------------------
1595
1596class GenericModel(GenericAircraftModel):
1597 """Generic aircraft model for an unknown type."""
1598 def __init__(self):
1599 """Construct the model."""
[8]1600 super(GenericModel, self). \
[7]1601 __init__(flapsNotches = [0, 10, 20, 30],
[140]1602 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
[7]1603 numEngines = 2)
1604
1605 @property
1606 def name(self):
1607 """Get the name for this aircraft model."""
[330]1608 return "FSUIPC/Generic"
[7]1609
1610#------------------------------------------------------------------------------
1611
1612class B737Model(GenericAircraftModel):
1613 """Generic model for the Boeing 737 Classing and NG aircraft."""
[330]1614 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1615
[7]1616 def __init__(self):
1617 """Construct the model."""
[8]1618 super(B737Model, self). \
[7]1619 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
[274]1620 fuelTanks = B737Model.fuelTanks,
[7]1621 numEngines = 2)
1622
1623 @property
1624 def name(self):
1625 """Get the name for this aircraft model."""
1626 return "FSUIPC/Generic Boeing 737"
1627
1628#------------------------------------------------------------------------------
1629
[8]1630class PMDGBoeing737NGModel(B737Model):
1631 """A model handler for the PMDG Boeing 737NG model."""
1632 @staticmethod
1633 def doesHandle(aircraft, (name, airPath)):
1634 """Determine if this model handler handles the aircraft with the given
1635 name."""
1636 return aircraft.type in [const.AIRCRAFT_B736,
1637 const.AIRCRAFT_B737,
[191]1638 const.AIRCRAFT_B738,
1639 const.AIRCRAFT_B738C] and \
[8]1640 (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
1641 (name.find("737")!=-1 or airPath.find("737")!=-1) and \
1642 (name.find("600")!=-1 or airPath.find("600")!=-1 or \
1643 name.find("700")!=-1 or airPath.find("700")!=-1 or \
1644 name.find("800")!=-1 or airPath.find("800")!=-1 or \
1645 name.find("900")!=-1 or airPath.find("900")!=-1)
1646
1647 @property
1648 def name(self):
1649 """Get the name for this aircraft model."""
[212]1650 return "FSUIPC/PMDG Boeing 737NG(X)"
[8]1651
[212]1652 def addMonitoringData(self, data, fsType):
[8]1653 """Add the model-specific monitoring data to the given array."""
[212]1654 self._fsType = fsType
[330]1655
[212]1656 super(PMDGBoeing737NGModel, self).addMonitoringData(data, fsType)
[330]1657
[212]1658 if fsType==const.SIM_MSFSX:
[476]1659 print "FSX detected, adding PMDG 737 NGX-specific offsets"
[212]1660 self._addOffsetWithIndexMember(data, 0x6500, "b",
1661 "_pmdgidx_lts_positionsw")
[476]1662 self._addOffsetWithIndexMember(data, 0x6545, "b", "_pmdgidx_cmda")
1663 self._addOffsetWithIndexMember(data, 0x653f, "b", "_pmdgidx_aphdgsel")
1664 self._addOffsetWithIndexMember(data, 0x6543, "b", "_pmdgidx_apalthold")
1665 self._addOffsetWithIndexMember(data, 0x652c, "H", "_pmdgidx_aphdg")
1666 self._addOffsetWithIndexMember(data, 0x652e, "H", "_pmdgidx_apalt")
1667 self._addOffsetWithIndexMember(data, 0x65cd, "b", "_pmdgidx_xpdr")
1668 else:
1669 print "FS9 detected, adding PMDG 737 NG-specific offsets"
1670 self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
1671 self._addOffsetWithIndexMember(data, 0x6216, "b", "_pmdgidx_xpdr")
1672 self._addOffsetWithIndexMember(data, 0x6227, "b", "_pmdgidx_ap")
1673 self._addOffsetWithIndexMember(data, 0x6228, "b", "_pmdgidx_aphdgsel")
1674 self._addOffsetWithIndexMember(data, 0x622a, "b", "_pmdgidx_apalthold")
1675 self._addOffsetWithIndexMember(data, 0x622c, "H", "_pmdgidx_aphdg")
1676 self._addOffsetWithIndexMember(data, 0x622e, "H", "_pmdgidx_apalt")
[212]1677
[8]1678 def getAircraftState(self, aircraft, timestamp, data):
1679 """Get the aircraft state.
1680
1681 Get it from the parent, and then check some PMDG-specific stuff."""
1682 state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
1683 timestamp,
1684 data)
[476]1685 if self._fsType==const.SIM_MSFS9:
1686 if data[self._pmdgidx_switches]&0x01==0x01:
1687 state.altimeter = 1013.25
1688 state.apMaster = data[self._pmdgidx_ap]&0x02==0x02
1689 state.apHeadingHold = data[self._pmdgidx_aphdgsel]==2
1690 apalthold = data[self._pmdgidx_apalthold]
1691 state.apAltitudeHold = apalthold>=3 and apalthold<=6
1692 else:
1693 state.apMaster = data[self._pmdgidx_cmda]!=0
1694 state.apHeadingHold = data[self._pmdgidx_aphdgsel]!=0
1695 state.apAltitudeHold = data[self._pmdgidx_apalthold]!=0
1696 #state.strobeLightsOn = data[self._pmdgidx_lts_positionsw]==0x02
1697 state.strobeLightsOn = None
[8]1698
[339]1699 state.xpdrC = data[self._pmdgidx_xpdr]==4
1700 state.apHeading = data[self._pmdgidx_aphdg]
1701 state.apAltitude = data[self._pmdgidx_apalt]
1702
[8]1703 return state
1704
1705#------------------------------------------------------------------------------
1706
[7]1707class B767Model(GenericAircraftModel):
1708 """Generic model for the Boeing 767 aircraft."""
[330]1709 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
[274]1710
[7]1711 def __init__(self):
1712 """Construct the model."""
[8]1713 super(B767Model, self). \
[7]1714 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
[381]1715 fuelTanks = B767Model.fuelTanks,
[7]1716 numEngines = 2)
1717
1718 @property
1719 def name(self):
1720 """Get the name for this aircraft model."""
1721 return "FSUIPC/Generic Boeing 767"
1722
1723#------------------------------------------------------------------------------
1724
1725class DH8DModel(GenericAircraftModel):
[16]1726 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
[330]1727 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1728
[7]1729 def __init__(self):
1730 """Construct the model."""
[8]1731 super(DH8DModel, self). \
[7]1732 __init__(flapsNotches = [0, 5, 10, 15, 35],
[274]1733 fuelTanks = DH8DModel.fuelTanks,
[7]1734 numEngines = 2)
1735
1736 @property
1737 def name(self):
1738 """Get the name for this aircraft model."""
[16]1739 return "FSUIPC/Generic Bombardier Dash 8-Q400"
1740
1741#------------------------------------------------------------------------------
1742
1743class DreamwingsDH8DModel(DH8DModel):
1744 """Model handler for the Dreamwings Dash 8-Q400."""
1745 @staticmethod
1746 def doesHandle(aircraft, (name, airPath)):
1747 """Determine if this model handler handles the aircraft with the given
1748 name."""
1749 return aircraft.type==const.AIRCRAFT_DH8D and \
1750 (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
1751 (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
1752 (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
1753 airPath.find("Dash8Q400")!=-1
[7]1754
[16]1755 @property
1756 def name(self):
1757 """Get the name for this aircraft model."""
1758 return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
1759
[456]1760 def addMonitoringData(self, data, fsType):
1761 """Add the model-specific monitoring data to the given array."""
1762 super(DreamwingsDH8DModel, self).addMonitoringData(data, fsType)
1763
1764 self._addOffsetWithIndexMember(data, 0x132c, "d", "_dwdh8d_navgps")
1765
[16]1766 def getAircraftState(self, aircraft, timestamp, data):
1767 """Get the aircraft state.
1768
1769 Get it from the parent, and then invert the pitot heat state."""
1770 state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
1771 timestamp,
1772 data)
[456]1773 if data[self._dwdh8d_navgps]==1:
1774 state.apHeading = None
1775
[16]1776 return state
[314]1777
[7]1778#------------------------------------------------------------------------------
1779
1780class CRJ2Model(GenericAircraftModel):
1781 """Generic model for the Bombardier CRJ-200 aircraft."""
[330]1782 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
[274]1783
[7]1784 def __init__(self):
1785 """Construct the model."""
[8]1786 super(CRJ2Model, self). \
[7]1787 __init__(flapsNotches = [0, 8, 20, 30, 45],
[274]1788 fuelTanks = CRJ2Model.fuelTanks,
[7]1789 numEngines = 2)
1790
1791 @property
1792 def name(self):
1793 """Get the name for this aircraft model."""
1794 return "FSUIPC/Generic Bombardier CRJ-200"
1795
1796#------------------------------------------------------------------------------
1797
1798class F70Model(GenericAircraftModel):
1799 """Generic model for the Fokker F70 aircraft."""
[330]1800 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
[274]1801
[7]1802 def __init__(self):
1803 """Construct the model."""
[8]1804 super(F70Model, self). \
[7]1805 __init__(flapsNotches = [0, 8, 15, 25, 42],
[274]1806 fuelTanks = F70Model.fuelTanks,
[7]1807 numEngines = 2)
1808
1809 @property
1810 def name(self):
1811 """Get the name for this aircraft model."""
1812 return "FSUIPC/Generic Fokker 70"
1813
1814#------------------------------------------------------------------------------
1815
[314]1816class DAF70Model(F70Model):
1817 """Model for the Digital Aviation F70 implementation on FS9."""
1818 @staticmethod
1819 def doesHandle(aircraft, (name, airPath)):
1820 """Determine if this model handler handles the aircraft with the given
1821 name."""
1822 return aircraft.type == const.AIRCRAFT_F70 and \
1823 (airPath.endswith("fokker70_2k4_v4.1.air") or
1824 airPath.endswith("fokker70_2k4_v4.3.air"))
1825
1826 @property
1827 def name(self):
1828 """Get the name for this aircraft model."""
1829 return "FSUIPC/Digital Aviation Fokker 70"
1830
1831 def getAircraftState(self, aircraft, timestamp, data):
1832 """Get the aircraft state.
1833
1834 Get it from the parent, and then invert the pitot heat state."""
1835 state = super(DAF70Model, self).getAircraftState(aircraft,
1836 timestamp,
1837 data)
[341]1838 state.navLightsOn = None
[314]1839 state.landingLightsOn = None
[366]1840
[394]1841 state.altimeterReliable = False
1842
[366]1843 state.ils = state.nav1
1844 state.ils_obs = state.nav1_obs
1845 state.ils_manual = state.nav1_manual
1846
1847 state.nav1 = state.nav2
1848 state.nav1_obs = state.nav2_obs
1849 state.nav1_manual = aircraft.flight.stage!=const.STAGE_CRUISE
1850
1851 state.nav2 = None
1852 state.nav2_obs = None
1853 state.nav2_manual = False
1854
1855 state.autoXPDR = True
[314]1856
1857 return state
1858
1859#------------------------------------------------------------------------------
1860
[7]1861class DC3Model(GenericAircraftModel):
1862 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
[289]1863 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
[330]1864 const.FUELTANK_RIGHT]
[274]1865 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1866 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1867
[7]1868 def __init__(self):
1869 """Construct the model."""
[8]1870 super(DC3Model, self). \
[7]1871 __init__(flapsNotches = [0, 15, 30, 45],
[274]1872 fuelTanks = DC3Model.fuelTanks,
[263]1873 numEngines = 2, isN1 = False)
[289]1874 self._leftLevel = 0.0
1875 self._rightLevel = 0.0
[7]1876
1877 @property
1878 def name(self):
1879 """Get the name for this aircraft model."""
[263]1880 return "FSUIPC/Generic Lisunov Li-2 (DC-3)"
[7]1881
[274]1882 def _convertFuelData(self, data, index = 0, addCapacities = False):
1883 """Convert the given data into a fuel info list.
1884
1885 It assumes to receive the 3 fuel tanks as seen above (left,
1886 centre and right) and converts it to left aux, left, right,
1887 and right aux. The amount in the left tank goes into left aux,
1888 the amount of the right tank goes into right aux and the
1889 amount of the centre tank goes into the left and right tanks
1890 evenly distributed."""
1891 (rawFuelData, totalFuel) = \
1892 super(DC3Model, self)._convertFuelData(data, index, addCapacities)
1893
1894 centreAmount = rawFuelData[1][1]
1895 if addCapacities:
1896 centreCapacity = rawFuelData[1][2]
[289]1897 self._leftLevel = self._rightLevel = \
1898 centreAmount / centreCapacity / 2.0
[330]1899 fuelData = [(const.FUELTANK_LEFT_AUX,
[274]1900 rawFuelData[0][1], rawFuelData[0][2]),
1901 (const.FUELTANK_LEFT,
1902 centreAmount/2.0, centreCapacity/2.0),
1903 (const.FUELTANK_RIGHT,
1904 centreAmount/2.0, centreCapacity/2.0),
1905 (const.FUELTANK_RIGHT_AUX,
1906 rawFuelData[2][1], rawFuelData[2][2])]
1907 else:
1908 fuelData = [(const.FUELTANK_LEFT_AUX, rawFuelData[0][1]),
1909 (const.FUELTANK_LEFT, centreAmount/2.0),
1910 (const.FUELTANK_RIGHT, centreAmount/2.0),
1911 (const.FUELTANK_RIGHT_AUX, rawFuelData[2][1])]
1912
1913 return (fuelData, totalFuel)
1914
1915 def setFuelLevel(self, handler, levels):
1916 """Set the fuel level.
1917
1918 See the description of Simulator.setFuelLevel. This
1919 implementation assumes to get the four-tank representation,
1920 as returned by getFuel()."""
[289]1921 leftLevel = None
1922 centreLevel = None
1923 rightLevel = None
[330]1924
[274]1925 for (tank, level) in levels:
[289]1926 if tank==const.FUELTANK_LEFT_AUX:
1927 leftLevel = level if leftLevel is None else (leftLevel + level)
1928 elif tank==const.FUELTANK_LEFT:
1929 level /= 2.0
1930 centreLevel = (self._rightLevel + level) \
1931 if centreLevel is None else (centreLevel + level)
1932 self._leftLevel = level
1933 elif tank==const.FUELTANK_RIGHT:
1934 level /= 2.0
1935 centreLevel = (self._leftLevel + level) \
1936 if centreLevel is None else (centreLevel + level)
1937 self._rightLevel = level
1938 elif tank==const.FUELTANK_RIGHT_AUX:
1939 rightLevel = level if rightLevel is None \
1940 else (rightLevel + level)
[274]1941
[289]1942 levels = []
1943 if leftLevel is not None: levels.append((const.FUELTANK_LEFT,
1944 leftLevel))
1945 if centreLevel is not None: levels.append((const.FUELTANK_CENTRE,
1946 centreLevel))
1947 if rightLevel is not None: levels.append((const.FUELTANK_RIGHT,
1948 rightLevel))
1949
1950 super(DC3Model, self).setFuelLevel(handler, levels)
[274]1951
[7]1952#------------------------------------------------------------------------------
1953
1954class T134Model(GenericAircraftModel):
1955 """Generic model for the Tupolev Tu-134 aircraft."""
[274]1956 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1957 const.FUELTANK_LEFT_AUX,
1958 const.FUELTANK_CENTRE,
1959 const.FUELTANK_RIGHT_AUX,
1960 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1961
[7]1962 def __init__(self):
1963 """Construct the model."""
[8]1964 super(T134Model, self). \
[7]1965 __init__(flapsNotches = [0, 10, 20, 30],
[274]1966 fuelTanks = T134Model.fuelTanks,
[7]1967 numEngines = 2)
1968
1969 @property
1970 def name(self):
1971 """Get the name for this aircraft model."""
1972 return "FSUIPC/Generic Tupolev Tu-134"
1973
1974#------------------------------------------------------------------------------
1975
1976class T154Model(GenericAircraftModel):
1977 """Generic model for the Tupolev Tu-134 aircraft."""
[274]1978 fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1979 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
1980 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1981
[7]1982 def __init__(self):
1983 """Construct the model."""
[8]1984 super(T154Model, self). \
[7]1985 __init__(flapsNotches = [0, 15, 28, 45],
[274]1986 fuelTanks = T154Model.fuelTanks,
[7]1987 numEngines = 3)
1988
1989 @property
1990 def name(self):
1991 """Get the name for this aircraft model."""
1992 return "FSUIPC/Generic Tupolev Tu-154"
1993
[8]1994 def getAircraftState(self, aircraft, timestamp, data):
[7]1995 """Get an aircraft state object for the given monitoring data.
1996
1997 This removes the reverser value for the middle engine."""
[8]1998 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
[7]1999 del state.reverser[1]
2000 return state
2001
2002#------------------------------------------------------------------------------
2003
[368]2004class PTT154Model(T154Model):
2005 """Project Tupolev Tu-154."""
2006 @staticmethod
2007 def doesHandle(aircraft, (name, airPath)):
2008 """Determine if this model handler handles the aircraft with the given
2009 name."""
2010 print "PTT154Model.doesHandle", aircraft.type, name, airPath
2011 return aircraft.type==const.AIRCRAFT_T154 and \
[530]2012 (name.find("Tu-154")!=-1 or name.find("Tu154B")!=-1) and \
[368]2013 os.path.basename(airPath).startswith("154b_")
2014
2015 def __init__(self):
2016 """Construct the model."""
2017 super(PTT154Model, self).__init__()
2018 self._adf1 = None
2019 self._adf2 = None
2020 self._lastValue = None
[529]2021 self._fsType = None
[368]2022
2023 @property
2024 def name(self):
2025 """Get the name for this aircraft model."""
2026 return "FSUIPC/Project Tupolev Tu-154"
2027
[529]2028 def addMonitoringData(self, data, fsType):
2029 """Add the model-specific monitoring data to the given array.
2030
2031 It only stores the flight simulator type."""
2032 self._fsType = fsType
2033
[530]2034 super(PTT154Model, self).addMonitoringData(data, fsType)
[529]2035
[368]2036 def getAircraftState(self, aircraft, timestamp, data):
2037 """Get an aircraft state object for the given monitoring data.
2038
2039 This removes the reverser value for the middle engine."""
2040 state = super(PTT154Model, self).getAircraftState(aircraft, timestamp, data)
2041
2042 adf1 = state.adf1
2043 if self._adf1 is None:
2044 self._adf1 = self._adf2 = adf1
2045 elif adf1 != self._lastValue and adf1 != self._adf1 and \
2046 adf1 != self._adf2:
2047 if self._lastValue==self._adf2:
2048 self._adf1 = adf1
2049 else:
2050 self._adf2 = adf1
2051
2052 self._lastValue = adf1
2053 state.adf1 = self._adf1
2054 state.adf2 = self._adf2
2055
[529]2056 if self._fsType==const.SIM_MSFSX:
2057 state.xpdrC = None
2058
[368]2059 return state
2060
2061
2062#------------------------------------------------------------------------------
2063
[7]2064class YK40Model(GenericAircraftModel):
2065 """Generic model for the Yakovlev Yak-40 aircraft."""
[274]2066 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
[330]2067
[7]2068 def __init__(self):
2069 """Construct the model."""
[8]2070 super(YK40Model, self). \
[7]2071 __init__(flapsNotches = [0, 20, 35],
[274]2072 fuelTanks = YK40Model.fuelTanks,
[7]2073 numEngines = 2)
2074
2075 @property
2076 def name(self):
2077 """Get the name for this aircraft model."""
2078 return "FSUIPC/Generic Yakovlev Yak-40"
2079
2080#------------------------------------------------------------------------------
2081
[443]2082class B462Model(GenericAircraftModel):
2083 """Generic model for the British Aerospace BAe 146-200 aircraft."""
2084 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
2085 const.FUELTANK_RIGHT]
2086
2087 def __init__(self):
2088 """Construct the model."""
2089 super(B462Model, self). \
2090 __init__(flapsNotches = [0, 18, 24, 30, 33],
2091 fuelTanks = B462Model.fuelTanks,
2092 numEngines = 4)
2093
2094 @property
2095 def name(self):
2096 """Get the name for this aircraft model."""
2097 return "FSUIPC/Generic British Aerospace 146"
2098
2099 def getAircraftState(self, aircraft, timestamp, data):
2100 """Get an aircraft state object for the given monitoring data.
2101
2102 This removes the reverser value for the middle engine."""
2103 state = super(B462Model, self).getAircraftState(aircraft, timestamp, data)
2104 state.reverser = []
2105 return state
2106
2107#------------------------------------------------------------------------------
2108
[191]2109_genericModels = { const.AIRCRAFT_B736 : B737Model,
2110 const.AIRCRAFT_B737 : B737Model,
2111 const.AIRCRAFT_B738 : B737Model,
2112 const.AIRCRAFT_B738C : B737Model,
2113 const.AIRCRAFT_B733 : B737Model,
2114 const.AIRCRAFT_B734 : B737Model,
2115 const.AIRCRAFT_B735 : B737Model,
2116 const.AIRCRAFT_DH8D : DH8DModel,
2117 const.AIRCRAFT_B762 : B767Model,
2118 const.AIRCRAFT_B763 : B767Model,
[381]2119 const.AIRCRAFT_CRJ2 : CRJ2Model,
[191]2120 const.AIRCRAFT_F70 : F70Model,
2121 const.AIRCRAFT_DC3 : DC3Model,
2122 const.AIRCRAFT_T134 : T134Model,
2123 const.AIRCRAFT_T154 : T154Model,
[443]2124 const.AIRCRAFT_YK40 : YK40Model,
2125 const.AIRCRAFT_B462 : B462Model }
[8]2126
2127#------------------------------------------------------------------------------
2128
2129AircraftModel.registerSpecial(PMDGBoeing737NGModel)
[16]2130AircraftModel.registerSpecial(DreamwingsDH8DModel)
[314]2131AircraftModel.registerSpecial(DAF70Model)
[368]2132AircraftModel.registerSpecial(PTT154Model)
[8]2133
2134#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.