source: src/mlx/fsuipc.py@ 858:1f655516b7ae

Last change on this file since 858:1f655516b7ae was 794:f261b3ac325b, checked in by István Váradi <ivaradi@…>, 8 years ago

Messages displayed in the simulator are converted to normal strings (from unicode, if needed) and marked unimportant (re #303)

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