source: src/mlx/fsuipc.py@ 530:3fa228e58954

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

Fixed problems with the detection of the FSX Tu-154 (re #208)

File size: 81.6 KB
Line 
1
2import fs
3import const
4import util
5import acft
6
7import threading
8import os
9import time
10import calendar
11import sys
12import codecs
13import math
14
15if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
16 import pyuipc
17else:
18 import pyuipc_sim as pyuipc
19
20#------------------------------------------------------------------------------
21
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
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
71class Handler(threading.Thread):
72 """The thread to handle the FSUIPC requests."""
73 @staticmethod
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
101 def _callSafe(fun):
102 """Call the given function and swallow any exceptions."""
103 try:
104 return fun()
105 except Exception, e:
106 print >> sys.stderr, util.utf2unicode(str(e))
107 return None
108
109 # The number of times a read is attempted
110 NUM_READATTEMPTS = 3
111
112 # The number of connection attempts
113 NUM_CONNECTATTEMPTS = 3
114
115 # The interval between successive connect attempts
116 CONNECT_INTERVAL = 0.25
117
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
140 class Request(object):
141 """A simple, one-shot request."""
142 def __init__(self, forWrite, data, callback, extra, validator = None):
143 """Construct the request."""
144 self._forWrite = forWrite
145 self._data = data
146 self._callback = callback
147 self._extra = extra
148 self._validator = validator
149
150 def process(self, time):
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
155 if there is some lower-level communication problem."""
156 if self._forWrite:
157 pyuipc.write(self._data)
158 Handler._callSafe(lambda: self._callback(True, self._extra))
159 return True
160 else:
161 return Handler._performRead(self._data, self._callback,
162 self._extra, self._validator)
163
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
171 class PeriodicRequest(object):
172 """A periodic request."""
173 def __init__(self, id, period, data, callback, extra, validator):
174 """Construct the periodic request."""
175 self._id = id
176 self._period = period
177 self._nextFire = time.time()
178 self._data = data
179 self._preparedData = None
180 self._callback = callback
181 self._extra = extra
182 self._validator = validator
183
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
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.
200
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
206
207 if self._preparedData is None:
208 self._preparedData = pyuipc.prepare_data(self._data)
209 self._data = None
210
211 isOK = Handler._performRead(self._preparedData, self._callback,
212 self._extra, self._validator)
213
214 if isOK:
215 while self._nextFire <= time:
216 self._nextFire += self._period
217
218 return isOK
219
220 def fail(self):
221 """Handle the failure of this request."""
222 pass
223
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
229 def __init__(self, connectionListener,
230 connectAttempts = -1, connectInterval = 0.2):
231 """Construct the handler with the given connection listener."""
232 threading.Thread.__init__(self)
233
234 self._connectionListener = connectionListener
235 self._connectAttempts = connectAttempts
236 self._connectInterval = connectInterval
237
238 self._requestCondition = threading.Condition()
239 self._connectionRequested = False
240 self._connected = False
241
242 self._requests = []
243 self._nextPeriodicID = 1
244 self._periodicRequests = []
245
246 self.daemon = True
247
248 def requestRead(self, data, callback, extra = None, validator = None):
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:
256 - the values retrieved or None on error
257 - the extra parameter
258
259 It will be called in the handler's thread!
260 """
261 with self._requestCondition:
262 self._requests.append(Handler.Request(False, data, callback, extra,
263 validator))
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
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!
278 """
279 with self._requestCondition:
280 request = Handler.Request(True, data, callback, extra)
281 #print "fsuipc.Handler.requestWrite", request
282 self._requests.append(request)
283 self._requestCondition.notify()
284
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
292 def requestPeriodicRead(self, period, data, callback, extra = None,
293 validator = None):
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
303 request = Handler.PeriodicRequest(id, period, data, callback,
304 extra, validator)
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()
324
325 def disconnect(self):
326 """Disconnect from the flight simulator."""
327 with self._requestCondition:
328 self._requests = []
329 if self._connectionRequested:
330 self._connectionRequested = False
331 self._requestCondition.notify()
332
333 def clearRequests(self):
334 """Clear the outstanding one-shot requests."""
335 with self._requestCondition:
336 self._requests = []
337
338 def run(self):
339 """Perform the operation of the thread."""
340 while True:
341 self._waitConnectionRequest()
342
343 if self._connect()>0:
344 self._handleConnection()
345
346 self._disconnect()
347
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()
353
354 def _connect(self, autoReconnection = False, attempts = 0):
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 """
360 while self._connectionRequested:
361 if attempts>=self.NUM_CONNECTATTEMPTS:
362 self._connectionRequested = False
363 if autoReconnection:
364 Handler._callSafe(lambda:
365 self._connectionListener.disconnected())
366 else:
367 Handler._callSafe(lambda:
368 self._connectionListener.connectionFailed())
369 return 0
370
371 try:
372 attempts += 1
373 pyuipc.open(pyuipc.SIM_ANY)
374 description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
375 (pyuipc.fsuipc_version, pyuipc.lib_version,
376 pyuipc.fs_version)
377 if not autoReconnection:
378 fsType = const.SIM_MSFSX \
379 if pyuipc.fs_version == pyuipc.SIM_FSX \
380 else const.SIM_MSFS9
381
382 Handler._callSafe(lambda:
383 self._connectionListener.connected(fsType,
384 description))
385 self._connected = True
386 return attempts
387 except Exception, e:
388 print "fsuipc.Handler._connect: connection failed: " + \
389 util.utf2unicode(str(e)) + \
390 " (attempts: %d)" % (attempts,)
391 if attempts<self.NUM_CONNECTATTEMPTS:
392 time.sleep(self.CONNECT_INTERVAL)
393
394 def _handleConnection(self):
395 """Handle a living connection."""
396 with self._requestCondition:
397 while self._connectionRequested:
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
413 if self._requests or \
414 (timeout is not None and timeout <= 0.0):
415 return
416
417 self._requestCondition.wait(timeout)
418
419 def _disconnect(self):
420 """Disconnect from the flight simulator."""
421 print "fsuipc.Handler._disconnect"
422 if self._connected:
423 pyuipc.close()
424 self._connected = False
425
426 def _processRequest(self, request, time, attempts):
427 """Process the given request.
428
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."""
437 self._requestCondition.release()
438
439 #print "fsuipc.Handler._processRequest", request
440
441 needReconnect = False
442 try:
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 (" + \
449 util.utf2unicode(str(e)) + \
450 "), reconnecting (attempts=%d)." % (attempts,)
451 needReconnect = True
452
453 if needReconnect:
454 with self._requestCondition:
455 self._requests.insert(0, request)
456 self._disconnect()
457 return self._connect(autoReconnection = True, attempts = attempts)
458 else:
459 return 0
460 finally:
461 self._requestCondition.acquire()
462
463 def _processRequests(self):
464 """Process any pending requests.
465
466 Will be called with the request lock held."""
467 attempts = 0
468 while self._connectionRequested and self._periodicRequests:
469 self._periodicRequests.sort()
470 request = self._periodicRequests[0]
471
472 t = time.time()
473
474 if request.nextFire>t:
475 break
476
477 attempts = self._processRequest(request, t, attempts)
478
479 while self._connectionRequested and self._requests:
480 request = self._requests[0]
481 del self._requests[0]
482
483 attempts = self._processRequest(request, None, attempts)
484
485 return self._connectionRequested
486
487#------------------------------------------------------------------------------
488
489class Simulator(object):
490 """The simulator class representing the interface to the flight simulator
491 via FSUIPC."""
492 # The basic data that should be queried all the time once we are connected
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
498
499 normalData = timeData + \
500 [ (0x3d00, -256), # The name of the current aircraft
501 (0x3c00, -256), # The path of the current AIR file
502 (0x1274, "h") ] # Text display mode
503
504 flareData1 = [ (0x023a, "b"), # Seconds of time
505 (0x31e4, "d"), # Radio altitude
506 (0x02c8, "d") ] # Vertical speed
507
508 flareStartData = [ (0x0e90, "H"), # Ambient wind speed
509 (0x0e92, "H"), # Ambient wind direction
510 (0x0e8a, "H") ] # Visibility
511
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
521 TIME_SYNC_INTERVAL = 3.0
522
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
531 timestamp += data[4]
532
533 return timestamp
534
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))
547
548 data.append((offset + 3, "b", 0))
549
550 def __init__(self, connectionListener, connectAttempts = -1,
551 connectInterval = 0.2):
552 """Construct the simulator.
553
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.
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
564 time.
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."""
572 self._fsType = None
573 self._aircraft = None
574
575 self._handler = Handler(self,
576 connectAttempts = connectAttempts,
577 connectInterval = connectInterval)
578 self._connectionListener = connectionListener
579 self._handler.start()
580
581 self._scroll = False
582
583 self._syncTime = False
584 self._nextSyncTime = -1
585
586 self._normalRequestID = None
587
588 self._monitoringRequested = False
589 self._monitoring = False
590
591 self._aircraftName = None
592 self._aircraftModel = None
593
594 self._flareRequestID = None
595 self._flareRates = []
596 self._flareStart = None
597 self._flareStartFS = None
598
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
607 self._latin1decoder = codecs.getdecoder("iso-8859-1")
608 self._fuelCallback = None
609
610 def connect(self, aircraft):
611 """Initiate a connection to the simulator."""
612 self._aircraft = aircraft
613 self._aircraftName = None
614 self._aircraftModel = None
615 self._handler.connect()
616 if self._normalRequestID is None:
617 self._nextSyncTime = -1
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)
630
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
635 separate arguments."""
636 self._handler.requestRead([(0x13fc, "d")], self._handlePayloadCount,
637 extra = callback)
638
639 def requestTime(self, callback):
640 """Request the time from the simulator."""
641 self._handler.requestRead(Simulator.timeData, self._handleTime,
642 extra = callback)
643
644 def startMonitoring(self):
645 """Start the periodic monitoring of the aircraft and pass the resulting
646 state to the aircraft object periodically."""
647 assert not self._monitoringRequested
648 self._monitoringRequested = True
649
650 def stopMonitoring(self):
651 """Stop the periodic monitoring of the aircraft."""
652 assert self._monitoringRequested
653 self._monitoringRequested = False
654
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
676 def sendMessage(self, message, duration = 3,
677 _disconnect = False):
678 """Send a message to the pilot via the simulator.
679
680 duration is the number of seconds to keep the message displayed."""
681
682 print "fsuipc.Simulator.sendMessage:", message
683
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
692 #if _disconnect:
693 # print "fsuipc.Simulator.sendMessage(disconnect)", message
694
695 self._handler.requestWrite(data, self._handleMessageSent,
696 extra = _disconnect)
697
698 def getFuel(self, callback):
699 """Get the fuel information for the current model.
700
701 The callback will be called with a list of triplets with the following
702 items:
703 - the fuel tank identifier
704 - the current weight of the fuel in the tank (in kgs)
705 - the current total capacity of the tank (in kgs)."""
706 if self._aircraftModel is None:
707 self._fuelCallback = callback
708 else:
709 self._aircraftModel.getFuel(self._handler, callback)
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."""
719 if self._aircraftModel is not None:
720 self._aircraftModel.setFuelLevel(self._handler, levels)
721
722 def enableTimeSync(self):
723 """Enable the time synchronization."""
724 self._nextSyncTime = -1
725 self._syncTime = True
726
727 def disableTimeSync(self):
728 """Enable the time synchronization."""
729 self._syncTime = False
730 self._nextSyncTime = -1
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
745
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()
770
771 def disconnect(self, closingMessage = None, duration = 3):
772 """Disconnect from the simulator."""
773 assert not self._monitoringRequested
774
775 print "fsuipc.Simulator.disconnect", closingMessage, duration
776
777 self._stopNormal()
778 self.clearHotkeys()
779 if closingMessage is None:
780 self._handler.disconnect()
781 else:
782 self.sendMessage(closingMessage, duration = duration,
783 _disconnect = True)
784
785 def connected(self, fsType, descriptor):
786 """Called when a connection has been established to the flight
787 simulator of the given type."""
788 self._fsType = fsType
789 with self._hotkeyLock:
790 if self._hotkeys is not None:
791 self._hotkeySetGeneration += 1
792
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
811 def _startDefaultNormal(self):
812 """Start the default normal periodic request."""
813 assert self._normalRequestID is None
814 self._normalRequestID = \
815 self._handler.requestPeriodicRead(1.0,
816 Simulator.normalData,
817 self._handleNormal,
818 validator = self._validateNormal)
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
825 self._monitoring = False
826
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
831 def _handleNormal(self, data, extra):
832 """Handle the reply to the normal request.
833
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.
837 """
838 timestamp = Simulator._getTimestamp(data)
839
840 createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
841 if self._fuelCallback is not None:
842 self._aircraftModel.getFuel(self._handler, self._fuelCallback)
843 self._fuelCallback = None
844
845 self._scroll = data[7]!=0
846
847 if self._monitoringRequested and not self._monitoring:
848 self._stopNormal()
849 self._startMonitoring()
850 elif self._monitoring and not self._monitoringRequested:
851 self._stopNormal()
852 self._startDefaultNormal()
853 elif self._monitoring and self._aircraftModel is not None and \
854 not createdNewModel:
855 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
856 timestamp, data)
857
858 self._checkTimeSync(aircraftState)
859
860 self._aircraft.handleState(aircraftState)
861
862 def _checkTimeSync(self, aircraftState):
863 """Check if we need to synchronize the FS time."""
864 if not self._syncTime or aircraftState.paused or \
865 self._flareRequestID is not None:
866 self._nextSyncTime = -1
867 return
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
875
876 self._handler.requestWrite([(0x023a, "b", int(seconds))],
877 self._handleTimeSynced)
878
879 #print "Set the seconds to ", seconds
880
881 if self._nextSyncTime<0:
882 self._nextSyncTime = now
883
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
891
892 def _setAircraftName(self, timestamp, name, airPath):
893 """Set the name of the aicraft and if it is different from the
894 previous, create a new model for it.
895
896 If so, also notifty the aircraft about the change.
897
898 Return if a new model was created."""
899 name = self._latin1decoder(name)[0]
900 airPath = self._latin1decoder(airPath)[0]
901
902 aircraftName = (name, airPath)
903 if aircraftName==self._aircraftName:
904 return False
905
906 print "fsuipc.Simulator: new aircraft name and air file path: %s, %s" % \
907 (name, airPath)
908
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:
919 self._setAircraftModel(AircraftModel.create(self._aircraft,
920 aircraftName))
921
922 self._aircraft.modelChanged(timestamp, name, self._aircraftModel.name)
923
924 return needNew
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
932
933 if self._monitoring:
934 self._stopNormal()
935 self._startMonitoring()
936
937 def _startMonitoring(self):
938 """Start monitoring with the current aircraft model."""
939 data = Simulator.normalData[:]
940 self._aircraftModel.addMonitoringData(data, self._fsType)
941
942 self._normalRequestID = \
943 self._handler.requestPeriodicRead(1.0, data,
944 self._handleNormal,
945 validator = self._validateNormal)
946 self._monitoring = True
947
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 = \
962 self._handler.requestPeriodicRead(0.1,
963 Simulator.flareData2,
964 self._handleFlare2)
965 self._handler.requestRead(Simulator.flareStartData,
966 self._handleFlareStart)
967
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])
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)
1012
1013 def _handleTime(self, data, callback):
1014 """Callback for a time retrieval request."""
1015 callback(Simulator._getTimestamp(data))
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"))
1023
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)
1034
1035 def _handleMessageSent(self, success, disconnect):
1036 """Callback for a message sending request."""
1037 #print "fsuipc.Simulator._handleMessageSent", disconnect
1038 if disconnect:
1039 self._handler.disconnect()
1040
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
1047 data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]
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))
1100
1101 def _handleHotkeysWritten(self, success, (id, generation)):
1102 """Handle the result of the hotkeys having been written."""
1103 with self._hotkeyLock:
1104 if success and id==self._hotkeySetID and \
1105 generation==self._hotkeySetGeneration:
1106 data = [(offset + 3, "b") for offset in self._hotkeyOffets]
1107
1108 self._hotkeyRequestID = \
1109 self._handler.requestPeriodicRead(0.5, data,
1110 self._handleHotkeys,
1111 (id, generation))
1112
1113 def _handleHotkeys(self, data, (id, generation)):
1114 """Handle the hotkeys."""
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):
1136 """Callback for the hotkey-clearing write request."""
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
1143
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."""
1151 monitoringData = [("paused", 0x0264, "H"),
1152 ("latitude", 0x0560, "l"),
1153 ("longitude", 0x0568, "l"),
1154 ("frozen", 0x3364, "H"),
1155 ("replay", 0x0628, "d"),
1156 ("slew", 0x05dc, "H"),
1157 ("overspeed", 0x036d, "b"),
1158 ("stalled", 0x036c, "b"),
1159 ("onTheGround", 0x0366, "H"),
1160 ("zfw", 0x3bfc, "d"),
1161 ("grossWeight", 0x30c0, "f"),
1162 ("heading", 0x0580, "d"),
1163 ("pitch", 0x0578, "d"),
1164 ("bank", 0x057c, "d"),
1165 ("ias", 0x02bc, "d"),
1166 ("mach", 0x11c6, "H"),
1167 ("groundSpeed", 0x02b4, "d"),
1168 ("vs", 0x02c8, "d"),
1169 ("radioAltitude", 0x31e4, "d"),
1170 ("altitude", 0x0570, "l"),
1171 ("gLoad", 0x11ba, "H"),
1172 ("flapsControl", 0x0bdc, "d"),
1173 ("flapsLeft", 0x0be0, "d"),
1174 ("flapsRight", 0x0be4, "d"),
1175 ("lights", 0x0d0c, "H"),
1176 ("pitot", 0x029c, "b"),
1177 ("parking", 0x0bc8, "H"),
1178 ("gearControl", 0x0be8, "d"),
1179 ("noseGear", 0x0bec, "d"),
1180 ("spoilersArmed", 0x0bcc, "d"),
1181 ("spoilers", 0x0bd0, "d"),
1182 ("altimeter", 0x0330, "H"),
1183 ("qnh", 0x0ec6, "H"),
1184 ("nav1", 0x0350, "H"),
1185 ("nav1_obs", 0x0c4e, "H"),
1186 ("nav2", 0x0352, "H"),
1187 ("nav2_obs", 0x0c5e, "H"),
1188 ("adf1_main", 0x034c, "H"),
1189 ("adf1_ext", 0x0356, "H"),
1190 ("adf2_main", 0x02d4, "H"),
1191 ("adf2_ext", 0x02d6, "H"),
1192 ("squawk", 0x0354, "H"),
1193 ("windSpeed", 0x0e90, "H"),
1194 ("windDirection", 0x0e92, "H"),
1195 ("visibility", 0x0e8a, "H"),
1196 ("cog", 0x2ef8, "f"),
1197 ("xpdrC", 0x7b91, "b"),
1198 ("apMaster", 0x07bc, "d"),
1199 ("apHeadingHold", 0x07c8, "d"),
1200 ("apHeading", 0x07cc, "H"),
1201 ("apAltitudeHold", 0x07d0, "d"),
1202 ("apAltitude", 0x07d4, "u"),
1203 ("elevatorTrim", 0x2ea0, "f"),
1204 ("eng1DeIce", 0x08b2, "H"),
1205 ("eng2DeIce", 0x094a, "H"),
1206 ("propDeIce", 0x337c, "b"),
1207 ("structDeIce", 0x337d, "b")]
1208
1209 specialModels = []
1210
1211 @staticmethod
1212 def registerSpecial(clazz):
1213 """Register the given class as a special model."""
1214 AircraftModel.specialModels.append(clazz)
1215
1216 @staticmethod
1217 def findSpecial(aircraft, aircraftName):
1218 for specialModel in AircraftModel.specialModels:
1219 if specialModel.doesHandle(aircraft, aircraftName):
1220 return specialModel
1221 return None
1222
1223 @staticmethod
1224 def create(aircraft, aircraftName):
1225 """Create the model for the given aircraft name, and notify the
1226 aircraft about it."""
1227 specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
1228 if specialModel is not None:
1229 return specialModel()
1230 if aircraft.type in _genericModels:
1231 return _genericModels[aircraft.type]()
1232 else:
1233 return GenericModel()
1234
1235 @staticmethod
1236 def convertBCD(data, length):
1237 """Convert a data item encoded as BCD into a string of the given number
1238 of digits."""
1239 bcd = ""
1240 for i in range(0, length):
1241 digit = chr(ord('0') + (data&0x0f))
1242 data >>= 4
1243 bcd = digit + bcd
1244 return bcd
1245
1246 @staticmethod
1247 def convertFrequency(data):
1248 """Convert the given frequency data to a string."""
1249 bcd = AircraftModel.convertBCD(data, 4)
1250 return "1" + bcd[0:2] + "." + bcd[2:4]
1251
1252 @staticmethod
1253 def convertADFFrequency(main, ext):
1254 """Convert the given ADF frequency data to a string."""
1255 mainBCD = AircraftModel.convertBCD(main, 4)
1256 extBCD = AircraftModel.convertBCD(ext, 4)
1257
1258 return (extBCD[1] if extBCD[1]!="0" else "") + \
1259 mainBCD[1:] + "." + extBCD[3]
1260
1261 def __init__(self, flapsNotches):
1262 """Construct the aircraft model.
1263
1264 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1265 self._flapsNotches = flapsNotches
1266 self._xpdrReliable = False
1267
1268 @property
1269 def name(self):
1270 """Get the name for this aircraft model."""
1271 return "FSUIPC/Generic"
1272
1273 def doesHandle(self, aircraft, aircraftName):
1274 """Determine if the model handles the given aircraft name.
1275
1276 This default implementation returns False."""
1277 return False
1278
1279 def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
1280 """Add the given FSUIPC offset and type to the given array and a member
1281 attribute with the given name."""
1282 dest.append((offset, type))
1283 if attrName is not None:
1284 setattr(self, attrName, len(dest)-1)
1285
1286 def _addDataWithIndexMembers(self, dest, prefix, data):
1287 """Add FSUIPC data to the given array and also corresponding index
1288 member variables with the given prefix.
1289
1290 data is a list of triplets of the following items:
1291 - the name of the data item. The index member variable will have a name
1292 created by prepending the given prefix to this name.
1293 - the FSUIPC offset
1294 - the FSUIPC type
1295
1296 The latter two items will be appended to dest."""
1297 for (name, offset, type) in data:
1298 self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
1299
1300 def addMonitoringData(self, data, fsType):
1301 """Add the model-specific monitoring data to the given array."""
1302 self._addDataWithIndexMembers(data, "_monidx_",
1303 AircraftModel.monitoringData)
1304
1305 def getAircraftState(self, aircraft, timestamp, data):
1306 """Get an aircraft state object for the given monitoring data."""
1307 state = fs.AircraftState()
1308
1309 state.timestamp = timestamp
1310
1311 state.latitude = data[self._monidx_latitude] * \
1312 90.0 / 10001750.0 / 65536.0 / 65536.0
1313
1314 state.longitude = data[self._monidx_longitude] * \
1315 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
1316 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1317
1318 state.paused = data[self._monidx_paused]!=0 or \
1319 data[self._monidx_frozen]!=0 or \
1320 data[self._monidx_replay]!=0
1321 state.trickMode = data[self._monidx_slew]!=0
1322
1323 state.overspeed = data[self._monidx_overspeed]!=0
1324 state.stalled = data[self._monidx_stalled]!=0
1325 state.onTheGround = data[self._monidx_onTheGround]!=0
1326
1327 state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
1328 state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
1329
1330 state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
1331
1332 state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
1333 state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
1334
1335 state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
1336 state.mach = data[self._monidx_mach] / 20480.0
1337 state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
1338 state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
1339
1340 state.radioAltitude = \
1341 Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
1342 state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
1343
1344 state.gLoad = data[self._monidx_gLoad] / 625.0
1345
1346 numNotchesM1 = len(self._flapsNotches) - 1
1347 flapsIncrement = 16383 / numNotchesM1
1348 flapsControl = data[self._monidx_flapsControl]
1349 flapsIndex = flapsControl / flapsIncrement
1350 if flapsIndex < numNotchesM1:
1351 if (flapsControl - (flapsIndex*flapsIncrement) >
1352 (flapsIndex+1)*flapsIncrement - flapsControl):
1353 flapsIndex += 1
1354 state.flapsSet = self._flapsNotches[flapsIndex]
1355
1356 flapsLeft = data[self._monidx_flapsLeft]
1357 state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
1358
1359 lights = data[self._monidx_lights]
1360
1361 state.navLightsOn = (lights&0x01) != 0
1362 state.antiCollisionLightsOn = (lights&0x02) != 0
1363 state.landingLightsOn = (lights&0x04) != 0
1364 state.strobeLightsOn = (lights&0x10) != 0
1365
1366 state.pitotHeatOn = data[self._monidx_pitot]!=0
1367
1368 state.parking = data[self._monidx_parking]!=0
1369
1370 state.gearControlDown = data[self._monidx_gearControl]==16383
1371 state.gearsDown = data[self._monidx_noseGear]==16383
1372
1373 state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
1374
1375 spoilers = data[self._monidx_spoilers]
1376 if spoilers<=4800:
1377 state.spoilersExtension = 0.0
1378 else:
1379 state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
1380
1381 state.altimeter = data[self._monidx_altimeter] / 16.0
1382 state.altimeterReliable = True
1383 state.qnh = data[self._monidx_qnh] / 16.0
1384
1385 state.ils = None
1386 state.ils_obs = None
1387 state.ils_manual = False
1388 state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
1389 state.nav1_obs = data[self._monidx_nav1_obs]
1390 state.nav1_manual = True
1391 state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
1392 state.nav2_obs = data[self._monidx_nav2_obs]
1393 state.nav2_manual = True
1394 state.adf1 = \
1395 AircraftModel.convertADFFrequency(data[self._monidx_adf1_main],
1396 data[self._monidx_adf1_ext])
1397 state.adf2 = \
1398 AircraftModel.convertADFFrequency(data[self._monidx_adf2_main],
1399 data[self._monidx_adf2_ext])
1400
1401 state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
1402
1403 state.windSpeed = data[self._monidx_windSpeed]
1404 state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
1405 if state.windDirection<0.0: state.windDirection += 360.0
1406
1407 state.visibility = data[self._monidx_visibility]*1609.344/100.0
1408
1409 state.cog = data[self._monidx_cog]
1410
1411 if not self._xpdrReliable:
1412 self._xpdrReliable = data[self._monidx_xpdrC]!=0
1413
1414 state.xpdrC = data[self._monidx_xpdrC]!=1 \
1415 if self._xpdrReliable else None
1416 state.autoXPDR = False
1417
1418 state.apMaster = data[self._monidx_apMaster]!=0
1419 state.apHeadingHold = data[self._monidx_apHeadingHold]!=0
1420 state.apHeading = data[self._monidx_apHeading] * 360.0 / 65536.0
1421 state.apAltitudeHold = data[self._monidx_apAltitudeHold]!=0
1422 state.apAltitude = data[self._monidx_apAltitude] / \
1423 const.FEETTOMETRES / 65536.0
1424
1425
1426 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1427
1428 state.antiIceOn = data[self._monidx_eng1DeIce]!=0 or \
1429 data[self._monidx_eng2DeIce]!=0 or \
1430 data[self._monidx_propDeIce]!=0 or \
1431 data[self._monidx_structDeIce]!=0
1432
1433 return state
1434
1435#------------------------------------------------------------------------------
1436
1437class GenericAircraftModel(AircraftModel):
1438 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1439 values and some other common parameters in a generic way."""
1440
1441 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1442 """Construct the generic aircraft model with the given data.
1443
1444 flapsNotches is an array of how much degrees the individual flaps
1445 notches mean.
1446
1447 fuelTanks is an array of const.FUELTANK_XXX constants about the
1448 aircraft's fuel tanks. They will be converted to offsets.
1449
1450 numEngines is the number of engines the aircraft has.
1451
1452 isN1 determines if the engines have an N1 value or an RPM value
1453 (e.g. pistons)."""
1454 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1455
1456 self._fuelTanks = fuelTanks
1457 self._fuelStartIndex = None
1458 self._numEngines = numEngines
1459 self._engineStartIndex = None
1460 self._isN1 = isN1
1461
1462 def doesHandle(self, aircraft, aircraftName):
1463 """Determine if the model handles the given aircraft name.
1464
1465 This implementation returns True."""
1466 return True
1467
1468 def addMonitoringData(self, data, fsType):
1469 """Add the model-specific monitoring data to the given array."""
1470 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
1471
1472 self._fuelStartIndex = self._addFuelOffsets(data, "_monidx_fuelWeight")
1473
1474 self._engineStartIndex = len(data)
1475 for i in range(0, self._numEngines):
1476 self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h") # throttle lever
1477 if self._isN1:
1478 self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f") # N1
1479 else:
1480 self._addOffsetWithIndexMember(data, 0x0898 + i * 0x98, "H") # RPM
1481 self._addOffsetWithIndexMember(data, 0x08c8 + i * 0x98, "H") # RPM scaler
1482
1483 def getAircraftState(self, aircraft, timestamp, data):
1484 """Get the aircraft state.
1485
1486 Get it from the parent, and then add the data about the fuel levels and
1487 the engine parameters."""
1488 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1489 timestamp,
1490 data)
1491
1492 (state.fuel, state.totalFuel) = \
1493 self._convertFuelData(data, index = self._monidx_fuelWeight)
1494
1495 state.n1 = [] if self._isN1 else None
1496 state.rpm = None if self._isN1 else []
1497 itemsPerEngine = 2 if self._isN1 else 3
1498
1499 state.reverser = []
1500 for i in range(self._engineStartIndex,
1501 self._engineStartIndex +
1502 itemsPerEngine*self._numEngines,
1503 itemsPerEngine):
1504 state.reverser.append(data[i]<0)
1505 if self._isN1:
1506 state.n1.append(data[i+1])
1507 else:
1508 state.rpm.append(data[i+1] * data[i+2]/65536.0)
1509
1510 return state
1511
1512 def getFuel(self, handler, callback):
1513 """Get the fuel information for this model.
1514
1515 See Simulator.getFuel for more information. This
1516 implementation simply queries the fuel tanks given to the
1517 constructor."""
1518 data = []
1519 self._addFuelOffsets(data)
1520
1521 handler.requestRead(data, self._handleFuelRetrieved,
1522 extra = callback)
1523
1524 def setFuelLevel(self, handler, levels):
1525 """Set the fuel level.
1526
1527 See the description of Simulator.setFuelLevel. This
1528 implementation simply sets the fuel tanks as given."""
1529 data = []
1530 for (tank, level) in levels:
1531 offset = _tank2offset[tank]
1532 value = long(level * 128.0 * 65536.0)
1533 data.append( (offset, "u", value) )
1534
1535 handler.requestWrite(data, self._handleFuelWritten)
1536
1537 def _addFuelOffsets(self, data, weightIndexName = None):
1538 """Add the fuel offsets to the given data array.
1539
1540 If weightIndexName is not None, it will be the name of the
1541 fuel weight index.
1542
1543 Returns the index of the first fuel tank's data."""
1544 self._addOffsetWithIndexMember(data, 0x0af4, "H", weightIndexName)
1545
1546 fuelStartIndex = len(data)
1547 for tank in self._fuelTanks:
1548 offset = _tank2offset[tank]
1549 self._addOffsetWithIndexMember(data, offset, "u") # tank level
1550 self._addOffsetWithIndexMember(data, offset+4, "u") # tank capacity
1551
1552 return fuelStartIndex
1553
1554 def _convertFuelData(self, data, index = 0, addCapacities = False):
1555 """Convert the given data into a fuel info list.
1556
1557 The list consists of two or three-tuples of the following
1558 items:
1559 - the fuel tank ID,
1560 - the amount of the fuel in kg,
1561 - if addCapacities is True, the total capacity of the tank."""
1562 fuelWeight = data[index] / 256.0
1563 index += 1
1564
1565 result = []
1566 totalFuel = 0
1567 for fuelTank in self._fuelTanks:
1568 capacity = data[index+1] * fuelWeight * const.LBSTOKG
1569 if capacity>=1.0:
1570 amount = data[index] * capacity / 128.0 / 65536.0
1571
1572 result.append( (fuelTank, amount, capacity) if addCapacities
1573 else (fuelTank, amount))
1574 totalFuel += amount
1575 index += 2
1576
1577 return (result, totalFuel)
1578
1579 def _handleFuelRetrieved(self, data, callback):
1580 """Callback for a fuel retrieval request."""
1581 (fuelData, _totalFuel) = self._convertFuelData(data,
1582 addCapacities = True)
1583 callback(fuelData)
1584
1585 def _handleFuelWritten(self, success, extra):
1586 """Callback for a fuel setting request."""
1587 pass
1588
1589#------------------------------------------------------------------------------
1590
1591class GenericModel(GenericAircraftModel):
1592 """Generic aircraft model for an unknown type."""
1593 def __init__(self):
1594 """Construct the model."""
1595 super(GenericModel, self). \
1596 __init__(flapsNotches = [0, 10, 20, 30],
1597 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1598 numEngines = 2)
1599
1600 @property
1601 def name(self):
1602 """Get the name for this aircraft model."""
1603 return "FSUIPC/Generic"
1604
1605#------------------------------------------------------------------------------
1606
1607class B737Model(GenericAircraftModel):
1608 """Generic model for the Boeing 737 Classing and NG aircraft."""
1609 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1610
1611 def __init__(self):
1612 """Construct the model."""
1613 super(B737Model, self). \
1614 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1615 fuelTanks = B737Model.fuelTanks,
1616 numEngines = 2)
1617
1618 @property
1619 def name(self):
1620 """Get the name for this aircraft model."""
1621 return "FSUIPC/Generic Boeing 737"
1622
1623#------------------------------------------------------------------------------
1624
1625class PMDGBoeing737NGModel(B737Model):
1626 """A model handler for the PMDG Boeing 737NG model."""
1627 @staticmethod
1628 def doesHandle(aircraft, (name, airPath)):
1629 """Determine if this model handler handles the aircraft with the given
1630 name."""
1631 return aircraft.type in [const.AIRCRAFT_B736,
1632 const.AIRCRAFT_B737,
1633 const.AIRCRAFT_B738,
1634 const.AIRCRAFT_B738C] and \
1635 (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
1636 (name.find("737")!=-1 or airPath.find("737")!=-1) and \
1637 (name.find("600")!=-1 or airPath.find("600")!=-1 or \
1638 name.find("700")!=-1 or airPath.find("700")!=-1 or \
1639 name.find("800")!=-1 or airPath.find("800")!=-1 or \
1640 name.find("900")!=-1 or airPath.find("900")!=-1)
1641
1642 @property
1643 def name(self):
1644 """Get the name for this aircraft model."""
1645 return "FSUIPC/PMDG Boeing 737NG(X)"
1646
1647 def addMonitoringData(self, data, fsType):
1648 """Add the model-specific monitoring data to the given array."""
1649 self._fsType = fsType
1650
1651 super(PMDGBoeing737NGModel, self).addMonitoringData(data, fsType)
1652
1653 if fsType==const.SIM_MSFSX:
1654 print "FSX detected, adding PMDG 737 NGX-specific offsets"
1655 self._addOffsetWithIndexMember(data, 0x6500, "b",
1656 "_pmdgidx_lts_positionsw")
1657 self._addOffsetWithIndexMember(data, 0x6545, "b", "_pmdgidx_cmda")
1658 self._addOffsetWithIndexMember(data, 0x653f, "b", "_pmdgidx_aphdgsel")
1659 self._addOffsetWithIndexMember(data, 0x6543, "b", "_pmdgidx_apalthold")
1660 self._addOffsetWithIndexMember(data, 0x652c, "H", "_pmdgidx_aphdg")
1661 self._addOffsetWithIndexMember(data, 0x652e, "H", "_pmdgidx_apalt")
1662 self._addOffsetWithIndexMember(data, 0x65cd, "b", "_pmdgidx_xpdr")
1663 else:
1664 print "FS9 detected, adding PMDG 737 NG-specific offsets"
1665 self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
1666 self._addOffsetWithIndexMember(data, 0x6216, "b", "_pmdgidx_xpdr")
1667 self._addOffsetWithIndexMember(data, 0x6227, "b", "_pmdgidx_ap")
1668 self._addOffsetWithIndexMember(data, 0x6228, "b", "_pmdgidx_aphdgsel")
1669 self._addOffsetWithIndexMember(data, 0x622a, "b", "_pmdgidx_apalthold")
1670 self._addOffsetWithIndexMember(data, 0x622c, "H", "_pmdgidx_aphdg")
1671 self._addOffsetWithIndexMember(data, 0x622e, "H", "_pmdgidx_apalt")
1672
1673 def getAircraftState(self, aircraft, timestamp, data):
1674 """Get the aircraft state.
1675
1676 Get it from the parent, and then check some PMDG-specific stuff."""
1677 state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
1678 timestamp,
1679 data)
1680 if self._fsType==const.SIM_MSFS9:
1681 if data[self._pmdgidx_switches]&0x01==0x01:
1682 state.altimeter = 1013.25
1683 state.apMaster = data[self._pmdgidx_ap]&0x02==0x02
1684 state.apHeadingHold = data[self._pmdgidx_aphdgsel]==2
1685 apalthold = data[self._pmdgidx_apalthold]
1686 state.apAltitudeHold = apalthold>=3 and apalthold<=6
1687 else:
1688 state.apMaster = data[self._pmdgidx_cmda]!=0
1689 state.apHeadingHold = data[self._pmdgidx_aphdgsel]!=0
1690 state.apAltitudeHold = data[self._pmdgidx_apalthold]!=0
1691 #state.strobeLightsOn = data[self._pmdgidx_lts_positionsw]==0x02
1692 state.strobeLightsOn = None
1693
1694 state.xpdrC = data[self._pmdgidx_xpdr]==4
1695 state.apHeading = data[self._pmdgidx_aphdg]
1696 state.apAltitude = data[self._pmdgidx_apalt]
1697
1698 return state
1699
1700#------------------------------------------------------------------------------
1701
1702class B767Model(GenericAircraftModel):
1703 """Generic model for the Boeing 767 aircraft."""
1704 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1705
1706 def __init__(self):
1707 """Construct the model."""
1708 super(B767Model, self). \
1709 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1710 fuelTanks = B767Model.fuelTanks,
1711 numEngines = 2)
1712
1713 @property
1714 def name(self):
1715 """Get the name for this aircraft model."""
1716 return "FSUIPC/Generic Boeing 767"
1717
1718#------------------------------------------------------------------------------
1719
1720class DH8DModel(GenericAircraftModel):
1721 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1722 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1723
1724 def __init__(self):
1725 """Construct the model."""
1726 super(DH8DModel, self). \
1727 __init__(flapsNotches = [0, 5, 10, 15, 35],
1728 fuelTanks = DH8DModel.fuelTanks,
1729 numEngines = 2)
1730
1731 @property
1732 def name(self):
1733 """Get the name for this aircraft model."""
1734 return "FSUIPC/Generic Bombardier Dash 8-Q400"
1735
1736#------------------------------------------------------------------------------
1737
1738class DreamwingsDH8DModel(DH8DModel):
1739 """Model handler for the Dreamwings Dash 8-Q400."""
1740 @staticmethod
1741 def doesHandle(aircraft, (name, airPath)):
1742 """Determine if this model handler handles the aircraft with the given
1743 name."""
1744 return aircraft.type==const.AIRCRAFT_DH8D and \
1745 (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
1746 (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
1747 (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
1748 airPath.find("Dash8Q400")!=-1
1749
1750 @property
1751 def name(self):
1752 """Get the name for this aircraft model."""
1753 return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
1754
1755 def addMonitoringData(self, data, fsType):
1756 """Add the model-specific monitoring data to the given array."""
1757 super(DreamwingsDH8DModel, self).addMonitoringData(data, fsType)
1758
1759 self._addOffsetWithIndexMember(data, 0x132c, "d", "_dwdh8d_navgps")
1760
1761 def getAircraftState(self, aircraft, timestamp, data):
1762 """Get the aircraft state.
1763
1764 Get it from the parent, and then invert the pitot heat state."""
1765 state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
1766 timestamp,
1767 data)
1768 if data[self._dwdh8d_navgps]==1:
1769 state.apHeading = None
1770
1771 return state
1772
1773#------------------------------------------------------------------------------
1774
1775class CRJ2Model(GenericAircraftModel):
1776 """Generic model for the Bombardier CRJ-200 aircraft."""
1777 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1778
1779 def __init__(self):
1780 """Construct the model."""
1781 super(CRJ2Model, self). \
1782 __init__(flapsNotches = [0, 8, 20, 30, 45],
1783 fuelTanks = CRJ2Model.fuelTanks,
1784 numEngines = 2)
1785
1786 @property
1787 def name(self):
1788 """Get the name for this aircraft model."""
1789 return "FSUIPC/Generic Bombardier CRJ-200"
1790
1791#------------------------------------------------------------------------------
1792
1793class F70Model(GenericAircraftModel):
1794 """Generic model for the Fokker F70 aircraft."""
1795 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1796
1797 def __init__(self):
1798 """Construct the model."""
1799 super(F70Model, self). \
1800 __init__(flapsNotches = [0, 8, 15, 25, 42],
1801 fuelTanks = F70Model.fuelTanks,
1802 numEngines = 2)
1803
1804 @property
1805 def name(self):
1806 """Get the name for this aircraft model."""
1807 return "FSUIPC/Generic Fokker 70"
1808
1809#------------------------------------------------------------------------------
1810
1811class DAF70Model(F70Model):
1812 """Model for the Digital Aviation F70 implementation on FS9."""
1813 @staticmethod
1814 def doesHandle(aircraft, (name, airPath)):
1815 """Determine if this model handler handles the aircraft with the given
1816 name."""
1817 return aircraft.type == const.AIRCRAFT_F70 and \
1818 (airPath.endswith("fokker70_2k4_v4.1.air") or
1819 airPath.endswith("fokker70_2k4_v4.3.air"))
1820
1821 @property
1822 def name(self):
1823 """Get the name for this aircraft model."""
1824 return "FSUIPC/Digital Aviation Fokker 70"
1825
1826 def getAircraftState(self, aircraft, timestamp, data):
1827 """Get the aircraft state.
1828
1829 Get it from the parent, and then invert the pitot heat state."""
1830 state = super(DAF70Model, self).getAircraftState(aircraft,
1831 timestamp,
1832 data)
1833 state.navLightsOn = None
1834 state.landingLightsOn = None
1835
1836 state.altimeterReliable = False
1837
1838 state.ils = state.nav1
1839 state.ils_obs = state.nav1_obs
1840 state.ils_manual = state.nav1_manual
1841
1842 state.nav1 = state.nav2
1843 state.nav1_obs = state.nav2_obs
1844 state.nav1_manual = aircraft.flight.stage!=const.STAGE_CRUISE
1845
1846 state.nav2 = None
1847 state.nav2_obs = None
1848 state.nav2_manual = False
1849
1850 state.autoXPDR = True
1851
1852 return state
1853
1854#------------------------------------------------------------------------------
1855
1856class DC3Model(GenericAircraftModel):
1857 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
1858 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
1859 const.FUELTANK_RIGHT]
1860 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1861 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1862
1863 def __init__(self):
1864 """Construct the model."""
1865 super(DC3Model, self). \
1866 __init__(flapsNotches = [0, 15, 30, 45],
1867 fuelTanks = DC3Model.fuelTanks,
1868 numEngines = 2, isN1 = False)
1869 self._leftLevel = 0.0
1870 self._rightLevel = 0.0
1871
1872 @property
1873 def name(self):
1874 """Get the name for this aircraft model."""
1875 return "FSUIPC/Generic Lisunov Li-2 (DC-3)"
1876
1877 def _convertFuelData(self, data, index = 0, addCapacities = False):
1878 """Convert the given data into a fuel info list.
1879
1880 It assumes to receive the 3 fuel tanks as seen above (left,
1881 centre and right) and converts it to left aux, left, right,
1882 and right aux. The amount in the left tank goes into left aux,
1883 the amount of the right tank goes into right aux and the
1884 amount of the centre tank goes into the left and right tanks
1885 evenly distributed."""
1886 (rawFuelData, totalFuel) = \
1887 super(DC3Model, self)._convertFuelData(data, index, addCapacities)
1888
1889 centreAmount = rawFuelData[1][1]
1890 if addCapacities:
1891 centreCapacity = rawFuelData[1][2]
1892 self._leftLevel = self._rightLevel = \
1893 centreAmount / centreCapacity / 2.0
1894 fuelData = [(const.FUELTANK_LEFT_AUX,
1895 rawFuelData[0][1], rawFuelData[0][2]),
1896 (const.FUELTANK_LEFT,
1897 centreAmount/2.0, centreCapacity/2.0),
1898 (const.FUELTANK_RIGHT,
1899 centreAmount/2.0, centreCapacity/2.0),
1900 (const.FUELTANK_RIGHT_AUX,
1901 rawFuelData[2][1], rawFuelData[2][2])]
1902 else:
1903 fuelData = [(const.FUELTANK_LEFT_AUX, rawFuelData[0][1]),
1904 (const.FUELTANK_LEFT, centreAmount/2.0),
1905 (const.FUELTANK_RIGHT, centreAmount/2.0),
1906 (const.FUELTANK_RIGHT_AUX, rawFuelData[2][1])]
1907
1908 return (fuelData, totalFuel)
1909
1910 def setFuelLevel(self, handler, levels):
1911 """Set the fuel level.
1912
1913 See the description of Simulator.setFuelLevel. This
1914 implementation assumes to get the four-tank representation,
1915 as returned by getFuel()."""
1916 leftLevel = None
1917 centreLevel = None
1918 rightLevel = None
1919
1920 for (tank, level) in levels:
1921 if tank==const.FUELTANK_LEFT_AUX:
1922 leftLevel = level if leftLevel is None else (leftLevel + level)
1923 elif tank==const.FUELTANK_LEFT:
1924 level /= 2.0
1925 centreLevel = (self._rightLevel + level) \
1926 if centreLevel is None else (centreLevel + level)
1927 self._leftLevel = level
1928 elif tank==const.FUELTANK_RIGHT:
1929 level /= 2.0
1930 centreLevel = (self._leftLevel + level) \
1931 if centreLevel is None else (centreLevel + level)
1932 self._rightLevel = level
1933 elif tank==const.FUELTANK_RIGHT_AUX:
1934 rightLevel = level if rightLevel is None \
1935 else (rightLevel + level)
1936
1937 levels = []
1938 if leftLevel is not None: levels.append((const.FUELTANK_LEFT,
1939 leftLevel))
1940 if centreLevel is not None: levels.append((const.FUELTANK_CENTRE,
1941 centreLevel))
1942 if rightLevel is not None: levels.append((const.FUELTANK_RIGHT,
1943 rightLevel))
1944
1945 super(DC3Model, self).setFuelLevel(handler, levels)
1946
1947#------------------------------------------------------------------------------
1948
1949class T134Model(GenericAircraftModel):
1950 """Generic model for the Tupolev Tu-134 aircraft."""
1951 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1952 const.FUELTANK_LEFT_AUX,
1953 const.FUELTANK_CENTRE,
1954 const.FUELTANK_RIGHT_AUX,
1955 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1956
1957 def __init__(self):
1958 """Construct the model."""
1959 super(T134Model, self). \
1960 __init__(flapsNotches = [0, 10, 20, 30],
1961 fuelTanks = T134Model.fuelTanks,
1962 numEngines = 2)
1963
1964 @property
1965 def name(self):
1966 """Get the name for this aircraft model."""
1967 return "FSUIPC/Generic Tupolev Tu-134"
1968
1969#------------------------------------------------------------------------------
1970
1971class T154Model(GenericAircraftModel):
1972 """Generic model for the Tupolev Tu-134 aircraft."""
1973 fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1974 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
1975 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1976
1977 def __init__(self):
1978 """Construct the model."""
1979 super(T154Model, self). \
1980 __init__(flapsNotches = [0, 15, 28, 45],
1981 fuelTanks = T154Model.fuelTanks,
1982 numEngines = 3)
1983
1984 @property
1985 def name(self):
1986 """Get the name for this aircraft model."""
1987 return "FSUIPC/Generic Tupolev Tu-154"
1988
1989 def getAircraftState(self, aircraft, timestamp, data):
1990 """Get an aircraft state object for the given monitoring data.
1991
1992 This removes the reverser value for the middle engine."""
1993 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
1994 del state.reverser[1]
1995 return state
1996
1997#------------------------------------------------------------------------------
1998
1999class PTT154Model(T154Model):
2000 """Project Tupolev Tu-154."""
2001 @staticmethod
2002 def doesHandle(aircraft, (name, airPath)):
2003 """Determine if this model handler handles the aircraft with the given
2004 name."""
2005 print "PTT154Model.doesHandle", aircraft.type, name, airPath
2006 return aircraft.type==const.AIRCRAFT_T154 and \
2007 (name.find("Tu-154")!=-1 or name.find("Tu154B")!=-1) and \
2008 os.path.basename(airPath).startswith("154b_")
2009
2010 def __init__(self):
2011 """Construct the model."""
2012 super(PTT154Model, self).__init__()
2013 self._adf1 = None
2014 self._adf2 = None
2015 self._lastValue = None
2016 self._fsType = None
2017
2018 @property
2019 def name(self):
2020 """Get the name for this aircraft model."""
2021 return "FSUIPC/Project Tupolev Tu-154"
2022
2023 def addMonitoringData(self, data, fsType):
2024 """Add the model-specific monitoring data to the given array.
2025
2026 It only stores the flight simulator type."""
2027 self._fsType = fsType
2028
2029 super(PTT154Model, self).addMonitoringData(data, fsType)
2030
2031 def getAircraftState(self, aircraft, timestamp, data):
2032 """Get an aircraft state object for the given monitoring data.
2033
2034 This removes the reverser value for the middle engine."""
2035 state = super(PTT154Model, self).getAircraftState(aircraft, timestamp, data)
2036
2037 adf1 = state.adf1
2038 if self._adf1 is None:
2039 self._adf1 = self._adf2 = adf1
2040 elif adf1 != self._lastValue and adf1 != self._adf1 and \
2041 adf1 != self._adf2:
2042 if self._lastValue==self._adf2:
2043 self._adf1 = adf1
2044 else:
2045 self._adf2 = adf1
2046
2047 self._lastValue = adf1
2048 state.adf1 = self._adf1
2049 state.adf2 = self._adf2
2050
2051 if self._fsType==const.SIM_MSFSX:
2052 state.xpdrC = None
2053
2054 return state
2055
2056
2057#------------------------------------------------------------------------------
2058
2059class YK40Model(GenericAircraftModel):
2060 """Generic model for the Yakovlev Yak-40 aircraft."""
2061 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
2062
2063 def __init__(self):
2064 """Construct the model."""
2065 super(YK40Model, self). \
2066 __init__(flapsNotches = [0, 20, 35],
2067 fuelTanks = YK40Model.fuelTanks,
2068 numEngines = 2)
2069
2070 @property
2071 def name(self):
2072 """Get the name for this aircraft model."""
2073 return "FSUIPC/Generic Yakovlev Yak-40"
2074
2075#------------------------------------------------------------------------------
2076
2077class B462Model(GenericAircraftModel):
2078 """Generic model for the British Aerospace BAe 146-200 aircraft."""
2079 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
2080 const.FUELTANK_RIGHT]
2081
2082 def __init__(self):
2083 """Construct the model."""
2084 super(B462Model, self). \
2085 __init__(flapsNotches = [0, 18, 24, 30, 33],
2086 fuelTanks = B462Model.fuelTanks,
2087 numEngines = 4)
2088
2089 @property
2090 def name(self):
2091 """Get the name for this aircraft model."""
2092 return "FSUIPC/Generic British Aerospace 146"
2093
2094 def getAircraftState(self, aircraft, timestamp, data):
2095 """Get an aircraft state object for the given monitoring data.
2096
2097 This removes the reverser value for the middle engine."""
2098 state = super(B462Model, self).getAircraftState(aircraft, timestamp, data)
2099 state.reverser = []
2100 return state
2101
2102#------------------------------------------------------------------------------
2103
2104_genericModels = { const.AIRCRAFT_B736 : B737Model,
2105 const.AIRCRAFT_B737 : B737Model,
2106 const.AIRCRAFT_B738 : B737Model,
2107 const.AIRCRAFT_B738C : B737Model,
2108 const.AIRCRAFT_B733 : B737Model,
2109 const.AIRCRAFT_B734 : B737Model,
2110 const.AIRCRAFT_B735 : B737Model,
2111 const.AIRCRAFT_DH8D : DH8DModel,
2112 const.AIRCRAFT_B762 : B767Model,
2113 const.AIRCRAFT_B763 : B767Model,
2114 const.AIRCRAFT_CRJ2 : CRJ2Model,
2115 const.AIRCRAFT_F70 : F70Model,
2116 const.AIRCRAFT_DC3 : DC3Model,
2117 const.AIRCRAFT_T134 : T134Model,
2118 const.AIRCRAFT_T154 : T154Model,
2119 const.AIRCRAFT_YK40 : YK40Model,
2120 const.AIRCRAFT_B462 : B462Model }
2121
2122#------------------------------------------------------------------------------
2123
2124AircraftModel.registerSpecial(PMDGBoeing737NGModel)
2125AircraftModel.registerSpecial(DreamwingsDH8DModel)
2126AircraftModel.registerSpecial(DAF70Model)
2127AircraftModel.registerSpecial(PTT154Model)
2128
2129#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.