source: src/mlx/fsuipc.py@ 749:9a902bc9af01

version_0.37_maint
Last change on this file since 749:9a902bc9af01 was 749:9a902bc9af01, checked in by István Váradi <ivaradi@…>, 8 years ago

Added some debug printouts that may shed light on the causes of the gear speed checking problem (re #282)

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