source: src/mlx/fsuipc.py@ 401:15ad3c16ee11

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

The exception strings are converted from UTF-8 to unicode for proper logging (re #170)

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