source: src/mlx/xplane.py@ 1042:c1308fb39512

python3
Last change on this file since 1042:c1308fb39512 was 1042:c1308fb39512, checked in by István Váradi <ivaradi@…>, 2 years ago

Added support for the Zibo/LevelUp Boeing 737NG models

File size: 84.5 KB
Line 
1
2from . import fs
3from . import const
4from . import util
5from .watchdog import Watchdog
6
7import threading
8import time
9import calendar
10import datetime
11import sys
12import codecs
13import math
14from functools import total_ordering
15
16from xplra import XPlane, MultiGetter, MultiSetter, ProtocolException
17from xplra import TYPE_INT, TYPE_FLOAT, TYPE_DOUBLE
18from xplra import TYPE_FLOAT_ARRAY, TYPE_INT_ARRAY, TYPE_BYTE_ARRAY
19from xplra import HOTKEY_MODIFIER_SHIFT, HOTKEY_MODIFIER_CONTROL
20
21#------------------------------------------------------------------------------
22
23## @package mlx.xplane
24#
25# The module towards X-Plane
26#
27# This module implements the simulator interface to X-Plane via the
28# X-Plane Remote Access (xplra) plugin.
29
30#------------------------------------------------------------------------------
31
32_hgin2hpa = 1013.25 / 29.92
33
34_mps2knots = 3600.0 / 1852
35
36#------------------------------------------------------------------------------
37
38class Request(object):
39 """Base class for one-shot requests."""
40 def __init__(self, handler, callback, extra):
41 """Construct the request."""
42 self._handler = handler
43 self._callback = callback
44 self._extra = extra
45 self._result = None
46
47 def process(self, time):
48 """Process the request.
49
50 Return True if the request has succeeded, False if data validation
51 has failed for a reading request. An exception may also be thrown
52 if there is some lower-level communication problem."""
53 if self._process(time):
54 Handler._callSafe(lambda: self._callback(self._result,
55 self._extra))
56 return True
57 else:
58 return False
59
60 def fail(self):
61 """Handle the failure of this request."""
62 Handler._callSafe(lambda: self._callback(False, self._extra))
63
64class DataRequest(Request):
65 """A simple, one-shot data read or write request."""
66 def __init__(self, handler, forWrite, data, callback, extra,
67 validator = None):
68 """Construct the request."""
69 super(DataRequest, self).__init__(handler, callback, extra)
70
71 self._forWrite = forWrite
72 self._validator = validator
73
74 xplane = handler._xplane
75 self._multiBuffer = xplane.createMultiSetter() if forWrite \
76 else xplane.createMultiGetter()
77
78 Handler._setupMultiBuffer(self._multiBuffer,
79 [(d[0], d[1]) for d in data])
80
81 if forWrite:
82 index = 0
83 for (_, _, value) in data:
84 self._multiBuffer[index] = value
85 index += 1
86
87
88 def fail(self):
89 """Handle the failure of this request."""
90 if self._forWrite:
91 super(DataRequest, self).fail()
92 else:
93 Handler._callSafe(lambda: self._callback(None, self._extra))
94
95 def _process(self, time):
96 """Process the request."""
97 if self._forWrite:
98 self._multiBuffer.execute()
99 self._result = True
100 return True
101
102 try:
103 if Handler._performRead(self._multiBuffer,
104 self._extra, self._validator):
105 self._result = self._multiBuffer
106 return True
107 else:
108 return False
109 except ProtocolException as e:
110 self._result = None
111 return True
112
113class ShowMessageRequest(Request):
114 """Request to show a message in the simulator window."""
115 def __init__(self, handler, message, duration, callback, extra):
116 """Construct the request."""
117 super(ShowMessageRequest, self).__init__(handler,
118 callback, extra)
119 self._message = message
120 self._duration = duration
121
122 def _process(self, time):
123 """Process the request."""
124 self._handler._xplane.showMessage(self._message, self._duration)
125 self._result = True
126 return True
127
128class RegisterHotkeysRequest(Request):
129 """Request to register hotkeys with the simulator."""
130 def __init__(self, handler, hotkeyCodes, callback, extra):
131 """Construct the request."""
132 super(RegisterHotkeysRequest, self).__init__(handler,
133 callback,
134 extra)
135 self._hotkeyCodes = hotkeyCodes
136
137 def _process(self, time):
138 """Process the request."""
139 self._handler._xplane.registerHotkeys(self._hotkeyCodes)
140 self._result = True
141 return True
142
143class UnregisterHotkeysRequest(Request):
144 """Request to register hotkeys with the simulator."""
145 def _process(self, time):
146 """Process the request."""
147 self._handler._xplane.unregisterHotkeys()
148 self._result = True
149 return True
150
151@total_ordering
152class PeriodicRequest(object):
153 """A periodic request."""
154 def __init__(self, handler, id, period, callback, extra):
155 """Construct the periodic request."""
156 self._handler = handler
157 self._id = id
158 self._period = period
159 self._nextFire = time.time()
160 self._callback = callback
161 self._extra = extra
162 self._result = None
163
164 @property
165 def id(self):
166 """Get the ID of this periodic request."""
167 return self._id
168
169 @property
170 def nextFire(self):
171 """Get the next firing time."""
172 return self._nextFire
173
174 def process(self, now):
175 """Check if this request should be executed, and if so, do so.
176
177 now is the time at which the request is being executed. If this
178 function is called too early, nothing is done, and True is
179 returned.
180
181 Return True if the request has succeeded, False if data validation
182 has failed. An exception may also be thrown if there is some
183 lower-level communication problem."""
184 if now<self._nextFire:
185 return True
186
187 isOK = self._process(time)
188
189 if isOK:
190 Handler._callSafe(lambda: self._callback(self._result,
191 self._extra))
192 now = time.time()
193 while self._nextFire <= now:
194 self._nextFire += self._period
195
196 return isOK
197
198 def fail(self):
199 """Handle the failure of this request."""
200 pass
201
202 def __eq__(self, other):
203 """Equality comparison by the firing times"""
204 return self._nextFire == other._nextFire
205
206 def __ne__(self, other):
207 """Non-equality comparison by the firing times"""
208 return self._nextFire != other._nextFire
209
210 def __lt__(self, other):
211 """Less-than comparison by the firing times"""
212 return self._nextFire < other._nextFire
213
214class PeriodicDataRequest(PeriodicRequest):
215 """A periodic request."""
216 def __init__(self, handler, id, period, data, callback, extra,
217 validator):
218 """Construct the periodic request."""
219 super(PeriodicDataRequest, self).__init__(handler, id, period,
220 callback, extra)
221 self._validator = validator
222 self._multiGetter = handler._xplane.createMultiGetter()
223 Handler._setupMultiBuffer(self._multiGetter, data)
224
225 def _process(self, now):
226 """Process the request."""
227 if Handler._performRead(self._multiGetter,
228 self._extra, self._validator):
229 self._result = self._multiGetter
230 return True
231 else:
232 return False
233
234#------------------------------------------------------------------------------
235
236class HotkeysStateRequest(PeriodicRequest):
237 """Periodic hotkey query request."""
238 def _process(self, now):
239 """Process the request."""
240 self._result = self._handler._xplane.queryHotkeys()
241 return True
242
243#------------------------------------------------------------------------------
244
245class Handler(threading.Thread):
246 """The thread to handle the requests towards X-Plane."""
247 @staticmethod
248 def _callSafe(fun):
249 """Call the given function and swallow any exceptions."""
250 try:
251 return fun()
252 except Exception as e:
253 print(util.utf2unicode(str(e)), file=sys.stderr)
254 return None
255
256 # The number of times a read is attempted
257 NUM_READATTEMPTS = 3
258
259 # The number of connection attempts
260 NUM_CONNECTATTEMPTS = 3
261
262 # The interval between successive connect attempts
263 CONNECT_INTERVAL = 0.25
264
265 @staticmethod
266 def _setupMultiBuffer(buffer, dataSpec):
267 """Setup the given multi-dataref buffer for the given data
268 specification.
269
270 The specification is a list of tuples of two items:
271 - the name of the dataref
272 - the type of the dataref. It can be one of the following:
273 - an integer denoting the type. If it denotes an array type, the
274 length will be -1 (i.e. as many as returned when reading the
275 value), and the offset will be 0
276 - a tuple of two or three items:
277 - the first item is the type constant
278 - the second item is the length
279 - the third item is the offset, which defaults to 0."""
280 for (name, typeInfo) in dataSpec:
281 length = -1
282 offset = 0
283 type = 0
284 if isinstance(typeInfo, tuple):
285 type = typeInfo[0]
286 length = typeInfo[1]
287 offset = 0 if len(typeInfo)<3 else typeInfo[2]
288 else:
289 type = typeInfo
290
291 if type==TYPE_INT:
292 buffer.addInt(name)
293 elif type==TYPE_FLOAT:
294 buffer.addFloat(name)
295 elif type==TYPE_DOUBLE:
296 buffer.addDouble(name)
297 elif type==TYPE_FLOAT_ARRAY:
298 buffer.addFloatArray(name, length = length, offset = offset)
299 elif type==TYPE_INT_ARRAY:
300 buffer.addIntArray(name, length = length, offset = offset)
301 elif type==TYPE_BYTE_ARRAY:
302 buffer.addByteArray(name, length = length, offset = offset)
303 else:
304 raise TypeError("xplane.Handler._setupMultiBuffer: invalid type info: %s for dataref '%s'" % (typeInfo, name))
305
306
307
308 @staticmethod
309 def _performRead(multiGetter, extra, validator):
310 """Perform a read request.
311
312 If there is a validator, that will be called with the return values,
313 and if the values are wrong, the request is retried at most a certain
314 number of times.
315
316 Return True if the request has succeeded, False if validation has
317 failed during all attempts. An exception may also be thrown if there is
318 some lower-level communication problem."""
319 attemptsLeft = Handler.NUM_READATTEMPTS
320 while attemptsLeft>0:
321 try:
322 multiGetter.execute()
323 except ProtocolException as e:
324 print("xplane.Handler._performRead: " + str(e))
325 raise
326
327 if validator is None or \
328 Handler._callSafe(lambda: validator(multiGetter, extra)):
329 return True
330 else:
331 attemptsLeft -= 1
332 return False
333
334 def __init__(self, connectionListener,
335 connectAttempts = -1, connectInterval = 0.2):
336 """Construct the handler with the given connection listener."""
337 threading.Thread.__init__(self)
338
339 self._connectionListener = connectionListener
340 self._connectAttempts = connectAttempts
341 self._connectInterval = connectInterval
342
343 self._xplane = XPlane()
344
345 self._requestCondition = threading.Condition()
346 self._connectionRequested = False
347 self._connected = False
348
349 self._requests = []
350 self._nextPeriodicID = 1
351 self._periodicRequests = []
352
353 self._watchdogClient = Watchdog.get().addClient(2.0, "xplane.Handler")
354
355 self.daemon = True
356
357 def requestRead(self, data, callback, extra = None, validator = None):
358 """Request the reading of some data.
359
360 data is a list of tuples of the following items:
361 - the offset of the data as an integer
362 - the type letter of the data as a string
363
364 callback is a function that receives two pieces of data:
365 - the values retrieved or None on error
366 - the extra parameter
367
368 It will be called in the handler's thread!
369 """
370 with self._requestCondition:
371 self._requests.append(DataRequest(self, False, data,
372 callback, extra,
373 validator))
374 self._requestCondition.notify()
375
376 def requestWrite(self, data, callback, extra = None):
377 """Request the writing of some data.
378
379 data is a list of tuples of the following items:
380 - the offset of the data as an integer
381 - the type letter of the data as a string
382 - the data to write
383
384 callback is a function that receives two pieces of data:
385 - a boolean indicating if writing was successful
386 - the extra data
387 It will be called in the handler's thread!
388 """
389 with self._requestCondition:
390 request = DataRequest(self, True, data, callback, extra)
391 #print "xplane.Handler.requestWrite", request
392 self._requests.append(request)
393 self._requestCondition.notify()
394
395 def requestPeriodicRead(self, period, data, callback, extra = None,
396 validator = None):
397 """Request a periodic read of data.
398
399 period is a floating point number with the period in seconds.
400
401 This function returns an identifier which can be used to cancel the
402 request."""
403 with self._requestCondition:
404 id = self._nextPeriodicID
405 self._nextPeriodicID += 1
406 request = PeriodicDataRequest(self, id, period,
407 data, callback,
408 extra, validator)
409 self._periodicRequests.append(request)
410 self._requestCondition.notify()
411 return id
412
413 def clearPeriodic(self, id):
414 """Clear the periodic request with the given ID."""
415 with self._requestCondition:
416 for i in range(0, len(self._periodicRequests)):
417 if self._periodicRequests[i].id==id:
418 del self._periodicRequests[i]
419 return True
420 return False
421
422 def requestShowMessage(self, message, duration, callback, extra = None):
423 """Request showing a message in the simulator."""
424 with self._requestCondition:
425 self._requests.append(ShowMessageRequest(self,
426 message, duration,
427 callback, extra))
428 self._requestCondition.notify()
429
430 def registerHotkeys(self, hotkeys, callback, extra = None):
431 """Request registering the given hotkeys."""
432 with self._requestCondition:
433 self._requests.append(RegisterHotkeysRequest(self, hotkeys,
434 callback, extra))
435 self._requestCondition.notify()
436
437 def requestHotkeysState(self, period, callback, extra = None):
438 """Request a periodic query of the hotkey status."""
439 with self._requestCondition:
440 id = self._nextPeriodicID
441 self._nextPeriodicID += 1
442 request = HotkeysStateRequest(self, id, period, callback, extra)
443 self._periodicRequests.append(request)
444 self._requestCondition.notify()
445 return id
446
447 def unregisterHotkeys(self, callback, extra = None):
448 """Request unregistering the hotkeys."""
449 with self._requestCondition:
450 self._requests.append(UnregisterHotkeysRequest(self,
451 callback, extra))
452 self._requestCondition.notify()
453
454 def connect(self):
455 """Initiate the connection to the flight simulator."""
456 with self._requestCondition:
457 if not self._connectionRequested:
458 self._connectionRequested = True
459 self._requestCondition.notify()
460
461 def disconnect(self):
462 """Disconnect from the flight simulator."""
463 with self._requestCondition:
464 self._requests = []
465 if self._connectionRequested:
466 self._connectionRequested = False
467 self._requestCondition.notify()
468
469 def clearRequests(self):
470 """Clear the outstanding one-shot requests."""
471 with self._requestCondition:
472 self._requests = []
473
474 def run(self):
475 """Perform the operation of the thread."""
476 while True:
477 self._waitConnectionRequest()
478
479 if self._connect()>0:
480 self._handleConnection()
481
482 self._disconnect()
483
484 def _waitConnectionRequest(self):
485 """Wait for a connection request to arrive."""
486 with self._requestCondition:
487 while not self._connectionRequested:
488 self._requestCondition.wait()
489
490 def _connect(self, autoReconnection = False, attempts = 0):
491 """Try to connect to the flight simulator via XPLRA
492
493 Returns True if the connection has been established, False if it was
494 not due to no longer requested.
495 """
496 while self._connectionRequested:
497 if attempts>=self.NUM_CONNECTATTEMPTS:
498 self._connectionRequested = False
499 if autoReconnection:
500 Handler._callSafe(lambda:
501 self._connectionListener.disconnected())
502 else:
503 Handler._callSafe(lambda:
504 self._connectionListener.connectionFailed())
505 return 0
506
507 try:
508 attempts += 1
509 self._xplane.connect()
510
511 (xplaneVersion, xplmVersion, xplraVersion) = \
512 self._xplane.getVersions()
513
514 description = "(X-Plane version: %d, XPLM version: %d, XPLRA version: %03d)" % \
515 (xplaneVersion, xplmVersion, xplraVersion)
516 if not autoReconnection:
517 fsType = const.SIM_XPLANE11 if xplaneVersion>=11000 else \
518 (const.SIM_XPLANE10 if xplaneVersion>=10000 else const.SIM_XPLANE9)
519
520 Handler._callSafe(lambda:
521 self._connectionListener.connected(fsType,
522 description))
523 self._connected = True
524 return attempts
525 except Exception as e:
526 print("xplane.Handler._connect: connection failed: " + \
527 util.utf2unicode(str(e)) + \
528 " (attempts: %d)" % (attempts,))
529 if attempts<self.NUM_CONNECTATTEMPTS:
530 time.sleep(self.CONNECT_INTERVAL)
531 self._xplane.disconnect()
532
533 def _handleConnection(self):
534 """Handle a living connection."""
535 with self._requestCondition:
536 while self._connectionRequested:
537 self._processRequests()
538 self._waitRequest()
539
540 def _waitRequest(self):
541 """Wait for the time of the next request.
542
543 Returns also, if the connection is no longer requested.
544
545 Should be called with the request condition lock held."""
546 while self._connectionRequested:
547 timeout = None
548 if self._periodicRequests:
549 self._periodicRequests.sort()
550 timeout = self._periodicRequests[0].nextFire - time.time()
551
552 if self._requests or \
553 (timeout is not None and timeout <= 0.0):
554 return
555
556 self._requestCondition.wait(timeout)
557
558 def _disconnect(self):
559 """Disconnect from the flight simulator."""
560 print("xplane.Handler._disconnect")
561 if self._connected:
562 try:
563 self._xplane.disconnect()
564 except:
565 pass
566
567 self._connected = False
568
569 def _processRequest(self, request, time, attempts):
570 """Process the given request.
571
572 If an exception occurs or invalid data is read too many times, we try
573 to reconnect.
574
575 This function returns only if the request has succeeded, or if a
576 connection is no longer requested.
577
578 This function is called with the request lock held, but is relased
579 whole processing the request and reconnecting."""
580 self._requestCondition.release()
581
582 #print "xplane.Handler._processRequest", request
583
584 needReconnect = False
585 try:
586 self._watchdogClient.set()
587 try:
588 if not request.process(time):
589 print("xplane.Handler._processRequest: X-Plane returned invalid data too many times, reconnecting")
590 needReconnect = True
591 except Exception as e:
592 print("xplane.Handler._processRequest: X-Plane connection failed (" + \
593 util.utf2unicode(str(e)) + \
594 "), reconnecting (attempts=%d)." % (attempts,))
595 needReconnect = True
596
597 if needReconnect:
598 with self._requestCondition:
599 self._requests.insert(0, request)
600 self._disconnect()
601 return self._connect(autoReconnection = True, attempts = attempts)
602 else:
603 return 0
604 finally:
605 self._watchdogClient.clear()
606 self._requestCondition.acquire()
607
608 def _processRequests(self):
609 """Process any pending requests.
610
611 Will be called with the request lock held."""
612 attempts = 0
613 while self._connectionRequested and self._periodicRequests:
614 self._periodicRequests.sort()
615 request = self._periodicRequests[0]
616
617 t = time.time()
618
619 if request.nextFire>t:
620 break
621
622 attempts = self._processRequest(request, t, attempts)
623
624 while self._connectionRequested and self._requests:
625 request = self._requests[0]
626 del self._requests[0]
627
628 attempts = self._processRequest(request, None, attempts)
629
630 return self._connectionRequested
631
632#------------------------------------------------------------------------------
633
634class Simulator(object):
635 """The simulator class representing the interface to the flight simulator
636 via XPLRA."""
637 # The basic data that should be queried all the time once we are connected
638 timeData = [ ("sim/time/local_date_days", TYPE_INT),
639 ("sim/time/zulu_time_sec", TYPE_FLOAT) ]
640
641 normalData = timeData + \
642 [ ("sim/aircraft/view/acf_tailnum", TYPE_BYTE_ARRAY),
643 ("sim/aircraft/view/acf_author", TYPE_BYTE_ARRAY),
644 ("sim/aircraft/view/acf_descrip", TYPE_BYTE_ARRAY),
645 ("sim/aircraft/view/acf_notes", TYPE_BYTE_ARRAY),
646 ("sim/aircraft/view/acf_ICAO", TYPE_BYTE_ARRAY),
647 ("sim/aircraft/view/acf_livery_path", TYPE_BYTE_ARRAY) ]
648
649 flareData1 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
650 ("sim/flightmodel/position/y_agl", TYPE_FLOAT),
651 ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT) ]
652
653 flareStartData = [ ("sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
654 ("sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
655 ("sim/weather/visibility_reported_m", TYPE_FLOAT) ]
656
657 flareData2 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
658 ("sim/flightmodel/failures/onground_any", TYPE_INT),
659 ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
660 ("sim/flightmodel/position/indicated_airspeed2",
661 TYPE_FLOAT),
662 ("sim/flightmodel/position/theta", TYPE_FLOAT),
663 ("sim/flightmodel/position/phi", TYPE_FLOAT),
664 ("sim/flightmodel/position/psi", TYPE_FLOAT) ]
665
666 TIME_SYNC_INTERVAL = 3.0
667
668 @staticmethod
669 def _getHotkeyCode(hotkey):
670 """Get the hotkey code for the given hot key."""
671 code = ord(hotkey.key)
672 if hotkey.shift: code |= HOTKEY_MODIFIER_SHIFT
673 if hotkey.ctrl: code |= HOTKEY_MODIFIER_CONTROL
674 return code
675
676 def __init__(self, connectionListener, connectAttempts = -1,
677 connectInterval = 0.2):
678 """Construct the simulator.
679
680 The aircraft object passed must provide the following members:
681 - type: one of the AIRCRAFT_XXX constants from const.py
682 - modelChanged(aircraftName, modelName): called when the model handling
683 the aircraft has changed.
684 - handleState(aircraftState): handle the given state.
685 - flareStarted(windSpeed, windDirection, visibility, flareStart,
686 flareStartFS): called when the flare has
687 started. windSpeed is in knots, windDirection is in degrees and
688 visibility is in metres. flareStart and flareStartFS are two time
689 values expressed in seconds that can be used to calculate the flare
690 time.
691 - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
692 ias, pitch, bank, heading): called when the flare has
693 finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
694 are the two time values corresponding to the touchdown time. tdRate is
695 the touch-down rate, tdRateCalculatedBySim indicates if the data comes
696 from the simulator or was calculated by the adapter. The other data
697 are self-explanatory and expressed in their 'natural' units."""
698 self._fsType = None
699 self._aircraft = None
700
701 self._handler = Handler(self,
702 connectAttempts = connectAttempts,
703 connectInterval = connectInterval)
704 self._connectionListener = connectionListener
705 self._handler.start()
706
707 self._syncTime = False
708 self._nextSyncTime = -1
709
710 self._timestampBase = None
711 self._timestampDaysOffset = 0
712 self._lastZuluSeconds = None
713
714 self._normalRequestID = None
715
716 self._monitoringRequested = False
717 self._monitoring = False
718
719 self._aircraftInfo = None
720 self._aircraftModel = None
721
722 self._flareRequestID = None
723 self._flareRates = []
724 self._flareStart = None
725 self._flareStartFS = None
726
727 self._hotkeyLock = threading.Lock()
728 self._hotkeyCodes = None
729 self._hotkeySetID = 0
730 self._hotkeySetGeneration = 0
731 self._hotkeyOffets = None
732 self._hotkeyRequestID = None
733 self._hotkeyCallback = None
734
735 self._fuelCallback = None
736
737 def connect(self, aircraft):
738 """Initiate a connection to the simulator."""
739 self._aircraft = aircraft
740 self._aircraftInfo = None
741 self._aircraftModel = None
742 self._handler.connect()
743 if self._normalRequestID is None:
744 self._nextSyncTime = -1
745 self._startDefaultNormal()
746
747 def reconnect(self):
748 """Initiate a reconnection to the simulator.
749
750 It does not reset already set up data, just calls connect() on the
751 handler."""
752 self._handler.connect()
753
754 def requestZFW(self, callback):
755 """Send a request for the ZFW."""
756 data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
757 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT) ]
758 self._handler.requestRead(data, self._handleZFW, extra = callback)
759
760 def requestWeights(self, callback):
761 """Request the following weights: DOW, ZFW, payload.
762
763 These values will be passed to the callback function in this order, as
764 separate arguments."""
765 data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
766 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
767 ("sim/flightmodel/weight/m_total", TYPE_FLOAT) ]
768 self._handler.requestRead(data, self._handleWeights,
769 extra = callback)
770
771 def requestTime(self, callback):
772 """Request the time from the simulator."""
773 self._handler.requestRead(Simulator.timeData, self._handleTime,
774 extra = callback)
775
776 def startMonitoring(self):
777 """Start the periodic monitoring of the aircraft and pass the resulting
778 state to the aircraft object periodically."""
779 assert not self._monitoringRequested
780 self._monitoringRequested = True
781
782 def stopMonitoring(self):
783 """Stop the periodic monitoring of the aircraft."""
784 assert self._monitoringRequested
785 self._monitoringRequested = False
786
787 def startFlare(self):
788 """Start monitoring the flare time.
789
790 At present it is assumed to be called from the handler thread, hence no
791 protection."""
792 #self._aircraft.logger.debug("startFlare")
793 if self._flareRequestID is None:
794 self._flareRates = []
795 self._flareRequestID = \
796 self._handler.requestPeriodicRead(0.1,
797 Simulator.flareData1,
798 self._handleFlare1)
799
800 def cancelFlare(self):
801 """Cancel monitoring the flare time.
802
803 At present it is assumed to be called from the handler thread, hence no
804 protection."""
805 if self._flareRequestID is not None:
806 self._handler.clearPeriodic(self._flareRequestID)
807 self._flareRequestID = None
808
809 def sendMessage(self, message, duration = 3,
810 _disconnect = False):
811 """Send a message to the pilot via the simulator.
812
813 duration is the number of seconds to keep the message displayed."""
814 print("xplra.Simulator.sendMessage:", message)
815 self._handler.requestShowMessage(message, duration,
816 self._handleMessageSent,
817 extra = _disconnect)
818
819 def getFuel(self, callback):
820 """Get the fuel information for the current model.
821
822 The callback will be called with a list of triplets with the following
823 items:
824 - the fuel tank identifier
825 - the current weight of the fuel in the tank (in kgs)
826 - the current total capacity of the tank (in kgs)."""
827 if self._aircraftModel is None:
828 self._fuelCallback = callback
829 else:
830 self._aircraftModel.getFuel(self._handler, callback)
831
832 def setFuelLevel(self, levels):
833 """Set the fuel level to the given ones.
834
835 levels is an array of two-tuples, where each tuple consists of the
836 following:
837 - the const.FUELTANK_XXX constant denoting the tank that must be set,
838 - the requested level of the fuel as a floating-point value between 0.0
839 and 1.0."""
840 if self._aircraftModel is not None:
841 self._aircraftModel.setFuelLevel(self._handler, levels)
842
843 def enableTimeSync(self):
844 """Enable the time synchronization."""
845 self._nextSyncTime = -1
846 self._syncTime = True
847
848 def disableTimeSync(self):
849 """Enable the time synchronization."""
850 self._syncTime = False
851 self._nextSyncTime = -1
852
853 def listenHotkeys(self, hotkeys, callback):
854 """Start listening to the given hotkeys.
855
856 callback is function expecting two arguments:
857 - the ID of the hotkey set as returned by this function,
858 - the list of the indexes of the hotkeys that were pressed."""
859 with self._hotkeyLock:
860 assert self._hotkeyCodes is None
861
862 self._hotkeyCodes = \
863 [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
864 self._hotkeySetID += 1
865 self._hotkeySetGeneration = 0
866 self._hotkeyCallback = callback
867
868 self._handler.registerHotkeys(self._hotkeyCodes,
869 self._handleHotkeysRegistered,
870 (self._hotkeySetID,
871 self._hotkeySetGeneration))
872
873 return self._hotkeySetID
874
875 def clearHotkeys(self):
876 """Clear the current hotkey set.
877
878 Note that it is possible, that the callback function set either
879 previously or after calling this function by listenHotkeys() will be
880 called with data from the previous hotkey set.
881
882 Therefore it is recommended to store the hotkey set ID somewhere and
883 check that in the callback function. Right before calling
884 clearHotkeys(), this stored ID should be cleared so that the check
885 fails for sure."""
886 with self._hotkeyLock:
887 if self._hotkeyCodes is not None:
888 self._hotkeyCodes = None
889 self._hotkeySetID += 1
890 self._hotkeyCallback = None
891 self._clearHotkeyRequest()
892
893 def disconnect(self, closingMessage = None, duration = 3):
894 """Disconnect from the simulator."""
895 assert not self._monitoringRequested
896
897 print("xplra.Simulator.disconnect", closingMessage, duration)
898
899 self._stopNormal()
900 self.clearHotkeys()
901 if closingMessage is None:
902 self._handler.disconnect()
903 else:
904 self.sendMessage(closingMessage, duration = duration,
905 _disconnect = True)
906
907 def connected(self, fsType, descriptor):
908 """Called when a connection has been established to the flight
909 simulator of the given type."""
910 self._fsType = fsType
911
912 with self._hotkeyLock:
913 if self._hotkeyCodes is not None:
914 self._hotkeySetGeneration += 1
915
916 self._handler.registerHotkeys(self._hotkeyCodes,
917 self._handleHotkeysRegistered,
918 (self._hotkeySetID,
919 self._hotkeySetGeneration))
920
921 self._connectionListener.connected(fsType, descriptor)
922
923 def connectionFailed(self):
924 """Called when the connection could not be established."""
925 with self._hotkeyLock:
926 self._clearHotkeyRequest()
927 self._connectionListener.connectionFailed()
928
929 def disconnected(self):
930 """Called when a connection to the flight simulator has been broken."""
931 with self._hotkeyLock:
932 self._clearHotkeyRequest()
933 self._connectionListener.disconnected()
934
935 def _getTimestamp(self, data):
936 """Convert the given data into a timestamp."""
937 if self._timestampBase is None:
938 year = datetime.date.today().year
939 self._timestampBase = \
940 calendar.timegm(time.struct_time([year, 1, 1, 0, 0, 0, -1, 1, 0]))
941 self._timestampBase += data[0] * 24 * 3600
942 self._timestampDaysOffset = 0
943 self._lastZuluSeconds = None
944
945 zuluSeconds = data[1]
946 if self._lastZuluSeconds is not None and \
947 zuluSeconds<self._lastZuluSeconds:
948 print("xplane.Simulator._getTimestamp: Zulu seconds have gone backwards (%f -> %f), increasing day offset" % \
949 (self._lastZuluSeconds, zuluSeconds))
950 self._timestampDaysOffset += 1
951
952 self._lastZuluSeconds = zuluSeconds
953
954 timestamp = self._timestampBase
955 timestamp += self._timestampDaysOffset * 24 * 3600
956 timestamp += zuluSeconds
957
958 return timestamp
959
960 def _startDefaultNormal(self):
961 """Start the default normal periodic request."""
962 assert self._normalRequestID is None
963 self._timestampBase = None
964 self._normalRequestID = \
965 self._handler.requestPeriodicRead(1.0,
966 Simulator.normalData,
967 self._handleNormal)
968
969 def _stopNormal(self):
970 """Stop the normal period request."""
971 assert self._normalRequestID is not None
972 self._handler.clearPeriodic(self._normalRequestID)
973 self._normalRequestID = None
974 self._monitoring = False
975
976 def _handleNormal(self, data, extra):
977 """Handle the reply to the normal request.
978
979 At the beginning the result consists the data for normalData. When
980 monitoring is started, it contains the result also for the
981 aircraft-specific values.
982 """
983 timestamp = self._getTimestamp(data)
984
985 createdNewModel = self._setAircraftName(timestamp,
986 data.getString(2),
987 data.getString(3),
988 data.getString(4),
989 data.getString(5),
990 data.getString(6),
991 data.getString(7))
992 if self._fuelCallback is not None:
993 self._aircraftModel.getFuel(self._handler, self._fuelCallback)
994 self._fuelCallback = None
995
996 if self._monitoringRequested and not self._monitoring:
997 self._stopNormal()
998 self._startMonitoring()
999 elif self._monitoring and not self._monitoringRequested:
1000 self._stopNormal()
1001 self._startDefaultNormal()
1002 elif self._monitoring and self._aircraftModel is not None and \
1003 not createdNewModel:
1004 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
1005 timestamp, data)
1006
1007 self._aircraft.handleState(aircraftState)
1008
1009 def _setAircraftName(self, timestamp, tailnum, author, description,
1010 notes, icao, liveryPath):
1011 """Set the name of the aicraft and if it is different from the
1012 previous, create a new model for it.
1013
1014 If so, also notifty the aircraft about the change.
1015
1016 Return if a new model was created."""
1017 aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
1018 if aircraftInfo==self._aircraftInfo:
1019 return False
1020
1021 print("xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
1022 (tailnum, author, description, notes, icao, liveryPath))
1023
1024 self._aircraftInfo = aircraftInfo
1025 needNew = self._aircraftModel is None
1026 needNew = needNew or\
1027 not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
1028 if not needNew:
1029 specialModel = AircraftModel.findSpecial(self._aircraft,
1030 aircraftInfo)
1031 needNew = specialModel is not None and \
1032 specialModel is not self._aircraftModel.__class__
1033
1034 if needNew:
1035 self._setAircraftModel(AircraftModel.create(self._aircraft,
1036 aircraftInfo))
1037
1038 self._aircraft.modelChanged(timestamp, description,
1039 self._aircraftModel.name)
1040
1041 return needNew
1042
1043 def _setAircraftModel(self, model):
1044 """Set a new aircraft model.
1045
1046 It will be queried for the data to monitor and the monitoring request
1047 will be replaced by a new one."""
1048 self._aircraftModel = model
1049 model.simulator = self
1050
1051 if self._monitoring:
1052 self._stopNormal()
1053 self._startMonitoring()
1054
1055 def _startMonitoring(self):
1056 """Start monitoring with the current aircraft model."""
1057 data = Simulator.normalData[:]
1058 self._aircraftModel.addMonitoringData(data, self._fsType)
1059
1060 self._normalRequestID = \
1061 self._handler.requestPeriodicRead(1.0, data,
1062 self._handleNormal)
1063 self._monitoring = True
1064
1065 def _addFlareRate(self, data):
1066 """Append a flare rate to the list of last rates."""
1067 if len(self._flareRates)>=3:
1068 del self._flareRates[0]
1069 self._flareRates.append(data)
1070
1071 def _handleFlare1(self, data, normal):
1072 """Handle the first stage of flare monitoring."""
1073 #self._aircraft.logger.debug("handleFlare1: " + str(data))
1074 if data[1]<=50.0*0.3048:
1075 self._flareStart = time.time()
1076 self._flareStartFS = data[0]
1077 self._handler.clearPeriodic(self._flareRequestID)
1078 self._handler.requestRead(Simulator.flareStartData,
1079 self._handleFlareStart)
1080
1081 self._addFlareRate(data[2])
1082
1083 def _handleFlareStart(self, data, extra):
1084 """Handle the data need to notify the aircraft about the starting of
1085 the flare."""
1086 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
1087 if data is not None:
1088 windDirection = data[1]
1089 if windDirection<0.0: windDirection += 360.0
1090 self._aircraft.flareStarted(data[0], windDirection, data[2],
1091 self._flareStart, self._flareStartFS)
1092
1093 self._flareRequestID = \
1094 self._handler.requestPeriodicRead(0.1,
1095 Simulator.flareData2,
1096 self._handleFlare2)
1097
1098 def _handleFlare2(self, data, normal):
1099 """Handle the first stage of flare monitoring."""
1100 #self._aircraft.logger.debug("handleFlare2: " + str(data))
1101 if data[1]!=0:
1102 flareEnd = time.time()
1103 self._handler.clearPeriodic(self._flareRequestID)
1104 self._flareRequestID = None
1105
1106 flareEndFS = data[0]
1107 if flareEndFS<self._flareStartFS:
1108 flareEndFS += 86400.0
1109
1110 tdRate = min(self._flareRates)
1111 tdRateCalculatedByFS = False
1112
1113 heading = data[6]
1114 if heading<0.0: heading += 360.0
1115
1116 self._aircraft.flareFinished(flareEnd, flareEndFS,
1117 tdRate, tdRateCalculatedByFS,
1118 data[3], data[4], data[5], heading)
1119 else:
1120 self._addFlareRate(data[2])
1121
1122 def _handleZFW(self, data, callback):
1123 """Callback for a ZFW retrieval request."""
1124 zfw = data[0] + data[1]
1125 callback(zfw)
1126
1127 def _handleTime(self, data, callback):
1128 """Callback for a time retrieval request."""
1129 callback(self._getTimestamp(data))
1130
1131 def _handleWeights(self, data, callback):
1132 """Callback for the weights retrieval request."""
1133 dow = data[0]
1134 payload = data[1]
1135 zfw = dow + payload
1136 grossWeight = data[2]
1137 callback(dow, payload, zfw, grossWeight)
1138
1139 def _handleMessageSent(self, success, disconnect):
1140 """Callback for a message sending request."""
1141 #print "xplra.Simulator._handleMessageSent", disconnect
1142 if disconnect:
1143 self._handler.disconnect()
1144
1145 def _handleHotkeysRegistered(self, success, hotkeySet):
1146 """Handle the result of the hotkeys having been written."""
1147 (id, generation) = hotkeySet
1148 with self._hotkeyLock:
1149 if success and id==self._hotkeySetID and \
1150 generation==self._hotkeySetGeneration:
1151 self._hotkeyRequestID = \
1152 self._handler.requestHotkeysState(0.5,
1153 self._handleHotkeys,
1154 hotkeySet)
1155
1156 def _handleHotkeys(self, data, hotkeySet):
1157 """Handle the hotkeys."""
1158 (id, generation) = hotkeySet
1159 with self._hotkeyLock:
1160 if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
1161 return
1162
1163 callback = self._hotkeyCallback
1164 offsets = self._hotkeyOffets
1165
1166 hotkeysPressed = []
1167 for i in range(0, len(data)):
1168 if data[i]:
1169 hotkeysPressed.append(i)
1170
1171 if hotkeysPressed:
1172 callback(id, hotkeysPressed)
1173
1174 def _clearHotkeyRequest(self):
1175 """Clear the hotkey request in the handler if there is any."""
1176 if self._hotkeyRequestID is not None:
1177 self._handler.unregisterHotkeys(self._hotkeysUnregistered)
1178 self._handler.clearPeriodic(self._hotkeyRequestID)
1179 self._hotkeyRequestID = None
1180
1181 def _hotkeysUnregistered(self, result, extra):
1182 """Called when the hotkeys have been unregistered."""
1183 pass
1184
1185#------------------------------------------------------------------------------
1186
1187class AircraftModel(object):
1188 """Base class for the aircraft models.
1189
1190 Aircraft models handle the data arriving from X-Plane and turn it into an
1191 object describing the aircraft's state."""
1192 monitoringData = [ ("paused",
1193 "sim/time/paused", TYPE_INT),
1194 ("latitude",
1195 "sim/flightmodel/position/latitude", TYPE_DOUBLE),
1196 ("longitude",
1197 "sim/flightmodel/position/longitude", TYPE_DOUBLE),
1198 ("replay",
1199 "sim/operation/prefs/replay_mode", TYPE_INT),
1200 ("overspeed",
1201 "sim/flightmodel/failures/over_vne", TYPE_INT),
1202 ("stalled",
1203 "sim/flightmodel/failures/stallwarning", TYPE_INT),
1204 ("onTheGround",
1205 "sim/flightmodel/failures/onground_any", TYPE_INT),
1206 ("emptyWeight",
1207 "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
1208 ("payloadWeight",
1209 "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
1210 ("grossWeight",
1211 "sim/flightmodel/weight/m_total", TYPE_FLOAT),
1212 ("heading",
1213 "sim/flightmodel/position/psi", TYPE_FLOAT),
1214 ("pitch",
1215 "sim/flightmodel/position/theta", TYPE_FLOAT),
1216 ("bank",
1217 "sim/flightmodel/position/phi", TYPE_FLOAT),
1218 ("ias",
1219 "sim/flightmodel/position/indicated_airspeed2",
1220 TYPE_FLOAT),
1221 ("mach",
1222 "sim/flightmodel/misc/machno", TYPE_FLOAT),
1223 ("groundSpeed",
1224 "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
1225 ("vs",
1226 "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
1227 ("radioAltitude",
1228 "sim/flightmodel/position/y_agl", TYPE_FLOAT),
1229 ("altitude",
1230 "sim/flightmodel/position/elevation", TYPE_FLOAT),
1231 ("gLoad",
1232 "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
1233 ("flapsControl",
1234 "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
1235 ("flapsLeft",
1236 "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
1237 ("flapsRight",
1238 "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
1239 ("navLights",
1240 "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
1241 ("beaconLights",
1242 "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
1243 ("strobeLights",
1244 "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
1245 ("landingLights",
1246 "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
1247 ("pitot",
1248 "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
1249 ("parking",
1250 "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
1251 ("gearControl",
1252 "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
1253 ("noseGear",
1254 "sim/flightmodel2/gear/deploy_ratio",
1255 (TYPE_FLOAT_ARRAY, 1)),
1256 ("spoilers",
1257 "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
1258 ("altimeter",
1259 "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
1260 ("qnh",
1261 "sim/physics/earth_pressure_p", TYPE_FLOAT),
1262 ("nav1",
1263 "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
1264 ("nav1_obs",
1265 "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
1266 ("nav2",
1267 "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
1268 ("nav2_obs",
1269 "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
1270 ("adf1",
1271 "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
1272 ("adf2",
1273 "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
1274 ("squawk",
1275 "sim/cockpit/radios/transponder_code", TYPE_INT),
1276 ("windSpeed",
1277 "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
1278 ("windDirection",
1279 "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
1280 ("visibility",
1281 "sim/weather/visibility_reported_m", TYPE_FLOAT),
1282 ("cog",
1283 "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
1284 ("xpdrC",
1285 "sim/cockpit/radios/transponder_mode", TYPE_INT),
1286 ("apMaster",
1287 "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
1288 ("apState",
1289 "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
1290 ("apHeading",
1291 "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
1292 ("apAltitude",
1293 "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
1294 ("elevatorTrim",
1295 "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
1296 ("antiIceOn",
1297 "sim/cockpit/switches/anti_ice_on", TYPE_INT),
1298 ("surfaceHeat",
1299 "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
1300 ("propHeat",
1301 "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
1302 ("autopilotOn",
1303 "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
1304 ("apHeadingMode",
1305 "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
1306
1307
1308 specialModels = []
1309
1310 @staticmethod
1311 def registerSpecial(clazz):
1312 """Register the given class as a special model."""
1313 AircraftModel.specialModels.append(clazz)
1314
1315 @staticmethod
1316 def findSpecial(aircraft, aircraftInfo):
1317 for specialModel in AircraftModel.specialModels:
1318 if specialModel.doesHandle(aircraft, aircraftInfo):
1319 return specialModel
1320 return None
1321
1322 @staticmethod
1323 def create(aircraft, aircraftInfo):
1324 """Create the model for the given aircraft name, and notify the
1325 aircraft about it."""
1326 specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
1327 if specialModel is not None:
1328 return specialModel()
1329 if aircraft.type in _genericModels:
1330 return _genericModels[aircraft.type]()
1331 else:
1332 return GenericModel()
1333
1334 @staticmethod
1335 def _convertFrequency(value):
1336 """Convert the given frequency value into a string."""
1337 return "%.2f" % (value/100.0,)
1338
1339 @staticmethod
1340 def _convertOBS(value):
1341 """Convert the given OBS value into an integer."""
1342 while value<0.0:
1343 value += 360.0
1344 return int(round(value))
1345
1346 def __init__(self, flapsNotches):
1347 """Construct the aircraft model.
1348
1349 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1350 self._flapsNotches = flapsNotches
1351 self._simulator = None
1352
1353 @property
1354 def name(self):
1355 """Get the name for this aircraft model."""
1356 return "X-Plane/Generic"
1357
1358 @property
1359 def simulator(self):
1360 """Get the simulator this aircraft model works for."""
1361 return self._simulator
1362
1363 @simulator.setter
1364 def simulator(self, simulator):
1365 """Get the simulator this aircraft model works for."""
1366 self._simulator = simulator
1367
1368 def doesHandle(self, aircraft, aircraftInfo):
1369 """Determine if the model handles the given aircraft name.
1370
1371 This default implementation returns False."""
1372 return False
1373
1374 def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
1375 """Add the given X-Plane dataref name and type to the given array and a
1376 member attribute with the given name."""
1377 dest.append((name, type))
1378 if attrName is not None:
1379 setattr(self, attrName, len(dest)-1)
1380
1381 def _addDataWithIndexMembers(self, dest, prefix, data):
1382 """Add X-Plane dataref data to the given array and also corresponding
1383 index member variables with the given prefix.
1384
1385 data is a list of triplets of the following items:
1386 - the name of the data item. The index member variable will have a name
1387 created by prepending the given prefix to this name.
1388 - the X-Plane dataref name
1389 - the dataref type
1390
1391 The latter two items will be appended to dest."""
1392 for (name, datarefName, type) in data:
1393 self._addDatarefWithIndexMember(dest, datarefName, type,
1394 prefix + name)
1395
1396 def addMonitoringData(self, data, fsType):
1397 """Add the model-specific monitoring data to the given array."""
1398 self._addDataWithIndexMembers(data, "_monidx_",
1399 AircraftModel.monitoringData)
1400
1401 def getAircraftState(self, aircraft, timestamp, data):
1402 """Get an aircraft state object for the given monitoring data."""
1403 state = fs.AircraftState()
1404
1405 lnavOn = data[self._monidx_autopilotOn]!=0 and \
1406 data[self._monidx_apHeadingMode]==2
1407
1408 state.timestamp = timestamp
1409
1410 state.latitude = data[self._monidx_latitude]
1411 state.longitude = data[self._monidx_longitude]
1412 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1413
1414 state.paused = data[self._monidx_paused]!=0 or \
1415 data[self._monidx_replay]!=0
1416 state.trickMode = data[self._monidx_replay]!=0
1417
1418 state.overspeed = data[self._monidx_overspeed]!=0
1419 state.stalled = data[self._monidx_stalled]!=0
1420 state.onTheGround = data[self._monidx_onTheGround]!=0
1421
1422 state.zfw = data[self._monidx_emptyWeight] + \
1423 data[self._monidx_payloadWeight]
1424 state.grossWeight = data[self._monidx_grossWeight]
1425
1426 state.heading = data[self._monidx_heading]
1427
1428 state.pitch = -1.0 * data[self._monidx_pitch]
1429 state.bank = data[self._monidx_bank]
1430
1431 state.ias = data[self._monidx_ias]
1432 state.mach = data[self._monidx_mach]
1433 state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
1434 state.vs = data[self._monidx_vs]
1435
1436 state.radioAltitude = data[self._monidx_radioAltitude]/.3048
1437 state.altitude = data[self._monidx_altitude]/.3048
1438
1439 state.gLoad = data[self._monidx_gLoad]
1440
1441 flapsControl = data[self._monidx_flapsControl]
1442 flapsIndex = int(round(flapsControl * (len(self._flapsNotches)-1)))
1443 state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
1444
1445 state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
1446
1447 state.navLightsOn = data[self._monidx_navLights] != 0
1448 state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
1449 state.landingLightsOn = data[self._monidx_landingLights] != 0
1450 state.strobeLightsOn = data[self._monidx_strobeLights] != 0
1451
1452 state.pitotHeatOn = data[self._monidx_pitot]!=0
1453
1454 state.parking = data[self._monidx_parking]>=0.5
1455
1456 state.gearControlDown = data[self._monidx_gearControl]!=0
1457 state.gearsDown = data[self._monidx_noseGear][0]>0.99
1458
1459 state.spoilersArmed = None
1460
1461 state.spoilersExtension = data[self._monidx_spoilers]*100.0
1462
1463 state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
1464 state.altimeterReliable = True
1465 state.qnh = data[self._monidx_qnh]/100.0
1466
1467 state.ils = None
1468 state.ils_obs = None
1469 state.ils_manual = False
1470 state.nav1 = self._convertFrequency(data[self._monidx_nav1])
1471 state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
1472 state.nav1_manual = True
1473 state.nav2 = self._convertFrequency(data[self._monidx_nav2])
1474 state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
1475 state.nav2_manual = not lnavOn
1476 state.adf1 = str(data[self._monidx_adf1])
1477 state.adf2 = str(data[self._monidx_adf2])
1478
1479 state.squawk = "%04d" % (data[self._monidx_squawk],)
1480
1481 state.windSpeed = data[self._monidx_windSpeed]
1482 state.windDirection = data[self._monidx_windDirection]
1483 if state.windDirection<0.0: state.windDirection += 360.0
1484
1485 state.visibility = data[self._monidx_visibility]
1486
1487 state.cog = data[self._monidx_cog]
1488
1489 state.xpdrC = data[self._monidx_xpdrC]==2
1490 state.autoXPDR = False
1491
1492 state.apMaster = data[self._monidx_apMaster]==2
1493 apState = data[self._monidx_apState]
1494 if lnavOn:
1495 state.apHeadingHold = None
1496 state.apHeading = None
1497 else:
1498 state.apHeadingHold = (apState&0x00002)!=0
1499 state.apHeading = data[self._monidx_apHeading]
1500
1501 state.apAltitudeHold = (apState&0x04000)!=0
1502 state.apAltitude = data[self._monidx_apAltitude]
1503
1504 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1505
1506 state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
1507 data[self._monidx_surfaceHeat]!=0 or \
1508 data[self._monidx_propHeat]!=0
1509
1510 return state
1511
1512#------------------------------------------------------------------------------
1513
1514class GenericAircraftModel(AircraftModel):
1515 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1516 values and some other common parameters in a generic way."""
1517
1518 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1519 """Construct the generic aircraft model with the given data.
1520
1521 flapsNotches is an array of how much degrees the individual flaps
1522 notches mean.
1523
1524 fuelTanks is an array of const.FUELTANK_XXX constants about the
1525 aircraft's fuel tanks. They will be converted to offsets.
1526
1527 numEngines is the number of engines the aircraft has.
1528
1529 isN1 determines if the engines have an N1 value or an RPM value
1530 (e.g. pistons)."""
1531 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1532
1533 self._fuelTanks = fuelTanks
1534 self._fuelTankCapacities = [1.0] * len(fuelTanks)
1535 self._fuelIndex = None
1536 self._numEngines = numEngines
1537 self._engineStartIndex = None
1538 self._isN1 = isN1
1539
1540 def doesHandle(self, aircraft, aircraftInfo):
1541 """Determine if the model handles the given aircraft name.
1542
1543 This implementation returns True."""
1544 return True
1545
1546 def addMonitoringData(self, data, fsType):
1547 """Add the model-specific monitoring data to the given array."""
1548 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
1549
1550 self._fuelIndex = self._addFuelData(data)
1551
1552 self._engineStartIndex = len(data)
1553 if self._isN1:
1554 self._addDatarefWithIndexMember(data,
1555 "sim/flightmodel/engine/ENGN_N1_",
1556 (TYPE_FLOAT_ARRAY,
1557 self._numEngines))
1558 else:
1559 self._addDatarefWithIndexMember(data,
1560 "sim/flightmodel/engine/POINT_tacrad",
1561 (TYPE_FLOAT_ARRAY,
1562 self._numEngines))
1563
1564 self._addDatarefWithIndexMember(data,
1565 "sim/flightmodel/engine/ENGN_propmode",
1566 (TYPE_INT_ARRAY, self._numEngines))
1567
1568 def getAircraftState(self, aircraft, timestamp, data):
1569 """Get the aircraft state.
1570
1571 Get it from the parent, and then add the data about the fuel levels and
1572 the engine parameters."""
1573 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1574 timestamp,
1575 data)
1576
1577 state.fuel = []
1578 state.totalFuel = 0.0
1579
1580 fuelAmounts = data[self._fuelIndex]
1581 for i in range(0, len(self._fuelTanks)):
1582 amount = fuelAmounts[i]
1583 state.fuel.append((self._fuelTanks[i], amount))
1584 state.totalFuel += amount
1585
1586 power = data[self._engineStartIndex]
1587
1588 state.n1 = power[:] if self._isN1 else None
1589 state.rpm = None if self._isN1 else power[:]
1590
1591 propMode = data[self._engineStartIndex+1]
1592 state.reverser = [mode == 3 for mode in propMode]
1593
1594 return state
1595
1596 def getFuel(self, handler, callback):
1597 """Get the fuel information for this model.
1598
1599 See Simulator.getFuel for more information. This
1600 implementation simply queries the fuel tanks given to the
1601 constructor."""
1602 data = []
1603 self._addFuelData(data)
1604 data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
1605 data.append( ("sim/aircraft/overflow/acf_tank_rat",
1606 (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
1607
1608 handler.requestRead(data, self._handleFuelRetrieved,
1609 extra = callback)
1610
1611 def setFuelLevel(self, handler, levels):
1612 """Set the fuel level.
1613
1614 See the description of Simulator.setFuelLevel. This
1615 implementation simply sets the fuel tanks as given."""
1616 data = []
1617 for (tank, level) in levels:
1618 try:
1619 index = self._fuelTanks.index(tank)
1620 data.append( ("sim/flightmodel/weight/m_fuel",
1621 (TYPE_FLOAT_ARRAY, 1, index),
1622 [level * self._fuelTankCapacities[index]]) )
1623 except:
1624 print("xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
1625 (tank,))
1626
1627 handler.requestWrite(data, self._handleFuelWritten)
1628
1629 def _addFuelData(self, data):
1630 """Add the fuel offsets to the given data array.
1631
1632 Returns the index of the first fuel tank's data."""
1633 fuelStartIndex = len(data)
1634 data.append( ("sim/flightmodel/weight/m_fuel",
1635 (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
1636
1637 return fuelStartIndex
1638
1639 def _convertFuelData(self, data, index = 0, addCapacities = False):
1640 """Convert the given data into a fuel info list.
1641
1642 The list consists of two or three-tuples of the following
1643 items:
1644 - the fuel tank ID,
1645 - the amount of the fuel in kg,
1646 - if addCapacities is True, the total capacity of the tank."""
1647 fuelWeight = data[index] / 256.0
1648 index += 1
1649
1650 result = []
1651 totalFuel = 0
1652 for fuelTank in self._fuelTanks:
1653 capacity = data[index+1] * fuelWeight * const.LBSTOKG
1654 if capacity>=1.0:
1655 amount = data[index] * capacity / 128.0 / 65536.0
1656
1657 result.append( (fuelTank, amount, capacity) if addCapacities
1658 else (fuelTank, amount))
1659 totalFuel += amount
1660 index += 2
1661
1662 return (result, totalFuel)
1663
1664 def _handleFuelRetrieved(self, data, callback):
1665 """Callback for a fuel retrieval request."""
1666 result = []
1667 totalCapacity = data[1]
1668 for index in range(0, len(self._fuelTanks)):
1669 amount = data[0][index]
1670 capacity = data[2][index] * totalCapacity
1671 self._fuelTankCapacities[index] = capacity
1672 result.append( (self._fuelTanks[index], amount, capacity) )
1673
1674 callback(result)
1675
1676 def _handleFuelWritten(self, success, extra):
1677 """Callback for a fuel setting request."""
1678 pass
1679
1680#------------------------------------------------------------------------------
1681
1682class GenericModel(GenericAircraftModel):
1683 """Generic aircraft model for an unknown type."""
1684 def __init__(self):
1685 """Construct the model."""
1686 super(GenericModel, self). \
1687 __init__(flapsNotches = [0, 10, 20, 30],
1688 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1689 numEngines = 2)
1690
1691 @property
1692 def name(self):
1693 """Get the name for this aircraft model."""
1694 return "X-Plane/Generic"
1695
1696#------------------------------------------------------------------------------
1697
1698class B737Model(GenericAircraftModel):
1699 """Generic model for the Boeing 737 Classing and NG aircraft."""
1700 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1701
1702 def __init__(self):
1703 """Construct the model."""
1704 super(B737Model, self). \
1705 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1706 fuelTanks = B737Model.fuelTanks,
1707 numEngines = 2)
1708
1709 @property
1710 def name(self):
1711 """Get the name for this aircraft model."""
1712 return "X-Plane/Generic Boeing 737"
1713
1714#------------------------------------------------------------------------------
1715
1716class ZiboB737NGModel(B737Model):
1717 """Base model for the Zibo and LevelUp Boeing 737 models."""
1718 def __init__(self, flapsRatios = [0.0, 0.081633, 0.142857, 0.224490,
1719 0.285714, 0.367347, 0.551020, 0.714286,
1720 1.0]):
1721 super(ZiboB737NGModel, self).__init__()
1722 self._flapsRatios = flapsRatios
1723
1724 def addMonitoringData(self, data, fsType):
1725 """Add the model-specific monitoring data to the given array."""
1726 super(ZiboB737NGModel, self).addMonitoringData(data, fsType)
1727
1728 self._speedBrakeIndex = len(data)
1729 self._addDatarefWithIndexMember(data,
1730 "sim/flightmodel2/wing/speedbrake1_deg",
1731 (TYPE_FLOAT_ARRAY, 2))
1732 self._addDatarefWithIndexMember(data,
1733 "sim/flightmodel2/wing/speedbrake2_deg",
1734 (TYPE_FLOAT_ARRAY, 2))
1735 self._cgIndex = len(data)
1736 self._addDatarefWithIndexMember(data,
1737 "laminar/B738/fms/calc_to_cg",
1738 TYPE_FLOAT)
1739 self._wingHeatIndex = len(data)
1740 self._addDatarefWithIndexMember(data,
1741 "laminar/B738/ice/wing_heat_pos",
1742 TYPE_FLOAT)
1743 self._eng1HeatIndex = len(data)
1744 self._addDatarefWithIndexMember(data,
1745 "laminar/B738/ice/eng1_heat_pos",
1746 TYPE_FLOAT)
1747 self._eng2HeatIndex = len(data)
1748 self._addDatarefWithIndexMember(data,
1749 "laminar/B738/ice/eng2_heat_pos",
1750 TYPE_FLOAT)
1751 self._spoilersArmedIndex = len(data)
1752 self._addDatarefWithIndexMember(data,
1753 "laminar/B738/annunciator/speedbrake_armed",
1754 TYPE_FLOAT)
1755
1756
1757 def getAircraftState(self, aircraft, timestamp, data):
1758 """Get the aircraft state."""
1759 state = super(ZiboB737NGModel, self).getAircraftState(aircraft,
1760 timestamp,
1761 data)
1762 state.cog = data[self._cgIndex]/100.0
1763
1764 flapsRatios = self._flapsRatios
1765 flapsRatio = data[self._monidx_flapsLeft]
1766 index = len(flapsRatios)
1767 for i in range(1, len(flapsRatios)):
1768 if flapsRatio<flapsRatios[i]:
1769 index = i-1
1770 break
1771 if index<len(flapsRatios):
1772 flapsRatio0 = flapsRatios[index]
1773 flapsNotch0 = self._flapsNotches[index]
1774 state.flaps = flapsNotch0 + \
1775 (self._flapsNotches[index+1] - flapsNotch0) * \
1776 (flapsRatio - flapsRatio0) / \
1777 (flapsRatios[index+1] - flapsRatio0)
1778 else:
1779 state.flaps = self._flapsNotches[-1]
1780
1781 # 0 -> -1
1782 # 15 -> 0.790881
1783 state.elevatorTrim = \
1784 15.0 * (data[self._monidx_elevatorTrim] + 1) / 1.790881
1785
1786 state.spoilersExtension = \
1787 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1788
1789 state.antiIceOn = data[self._wingHeatIndex]!=0 or \
1790 data[self._eng1HeatIndex]!=0 or \
1791 data[self._eng2HeatIndex]!=0
1792
1793 state.spoilersArmed = data[self._spoilersArmedIndex]!=0
1794
1795 return state
1796
1797#------------------------------------------------------------------------------
1798
1799class ZiboB738Model(ZiboB737NGModel):
1800 """Model for the Zibo Boeing 737-800 model."""
1801 @staticmethod
1802 def doesHandle(aircraft, data):
1803 """Determine if this model handler handles the aircraft with the given
1804 name."""
1805 (tailnum, author, description, notes, icao, liveryPath) = data
1806 return author=="Alex Unruh" and \
1807 description=="Boeing 737-800X" and \
1808 notes.startswith("ZIBOmod") and \
1809 icao=="B738"
1810
1811 def __init__(self):
1812 """Construct the model."""
1813 super(ZiboB738Model, self).__init__(
1814 flapsRatios = [0.0, 0.081633, 0.142857, 0.224490, 0.285714, 0.346939,
1815 0.551020, 0.673469, 1.0])
1816
1817 @property
1818 def name(self):
1819 """Get the name for this aircraft model."""
1820 return "Zibo Boeing 737-800"
1821
1822#------------------------------------------------------------------------------
1823
1824class LevelUpB736Model(ZiboB737NGModel):
1825 """Model for the LevelUp Boeing 737-600 model."""
1826
1827 @staticmethod
1828 def doesHandle(aircraft, data):
1829 """Determine if this model handler handles the aircraft with the given
1830 name."""
1831 (tailnum, author, description, notes, icao, liveryPath) = data
1832 return author=="Alex Unruh" and \
1833 description=="Boeing 737-600NG" and \
1834 icao=="B736"
1835
1836 @property
1837 def name(self):
1838 """Get the name for this aircraft model."""
1839 return "LevelUp Boeing 737-600"
1840
1841#------------------------------------------------------------------------------
1842
1843class LevelUpB737Model(ZiboB737NGModel):
1844 """Model for the LevelUp Boeing 737-700 model."""
1845
1846 @staticmethod
1847 def doesHandle(aircraft, data):
1848 """Determine if this model handler handles the aircraft with the given
1849 name."""
1850 (tailnum, author, description, notes, icao, liveryPath) = data
1851 return author=="Alex Unruh" and \
1852 description=="Boeing 737-700NG" and \
1853 icao=="B737"
1854
1855 @property
1856 def name(self):
1857 """Get the name for this aircraft model."""
1858 return "LevelUp Boeing 737-700"
1859
1860#------------------------------------------------------------------------------
1861
1862class LevelUpB738Model(ZiboB737NGModel):
1863 """Model for the LevelUp Boeing 737-800 model."""
1864
1865 @staticmethod
1866 def doesHandle(aircraft, data):
1867 """Determine if this model handler handles the aircraft with the given
1868 name."""
1869 (tailnum, author, description, notes, icao, liveryPath) = data
1870 return author=="Alex Unruh" and \
1871 description=="Boeing 737-800NG" and \
1872 icao=="B738"
1873
1874 @property
1875 def name(self):
1876 """Get the name for this aircraft model."""
1877 return "LevelUp Boeing 737-800"
1878
1879#------------------------------------------------------------------------------
1880
1881class B767Model(GenericAircraftModel):
1882 """Generic model for the Boeing 767 aircraft."""
1883 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1884
1885 def __init__(self):
1886 """Construct the model."""
1887 super(B767Model, self). \
1888 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1889 fuelTanks = B767Model.fuelTanks,
1890 numEngines = 2)
1891
1892 @property
1893 def name(self):
1894 """Get the name for this aircraft model."""
1895 return "X-Plane/Generic Boeing 767"
1896
1897#------------------------------------------------------------------------------
1898
1899class DH8DModel(GenericAircraftModel):
1900 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1901 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1902
1903 def __init__(self):
1904 """Construct the model."""
1905 super(DH8DModel, self). \
1906 __init__(flapsNotches = [0, 5, 10, 15, 35],
1907 fuelTanks = DH8DModel.fuelTanks,
1908 numEngines = 2)
1909
1910 @property
1911 def name(self):
1912 """Get the name for this aircraft model."""
1913 return "X-Plane/Generic Bombardier Dash 8-Q400"
1914
1915#------------------------------------------------------------------------------
1916
1917class FJSDH8DModel(DH8DModel):
1918 """Model handler for the FlyJSim Dash 8-Q400."""
1919 @staticmethod
1920 def doesHandle(aircraft, data):
1921 """Determine if this model handler handles the aircraft with the given
1922 name."""
1923 (tailnum, author, description, notes, icao, liveryPath) = data
1924 return aircraft.type==const.AIRCRAFT_DH8D and \
1925 description.find("Dash 8 Q400")!=-1 and \
1926 ((author in ["2012", "2013"] and tailnum=="N62890") or \
1927 author.find("Jack Skieczius")!=-1)
1928
1929 @property
1930 def name(self):
1931 """Get the name for this aircraft model."""
1932 return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
1933
1934 def addMonitoringData(self, data, fsType):
1935 """Add the model-specific monitoring data to the given array."""
1936 super(FJSDH8DModel, self).addMonitoringData(data, fsType)
1937
1938 self._speedBrakeIndex = len(data)
1939 self._addDatarefWithIndexMember(data,
1940 "sim/flightmodel2/wing/speedbrake1_deg",
1941 (TYPE_FLOAT_ARRAY, 2))
1942 self._addDatarefWithIndexMember(data,
1943 "sim/flightmodel2/wing/speedbrake2_deg",
1944 (TYPE_FLOAT_ARRAY, 2))
1945
1946
1947 def getAircraftState(self, aircraft, timestamp, data):
1948 """Get the aircraft state.
1949
1950 Get it from the parent, and then invert the pitot heat state."""
1951 state = super(FJSDH8DModel, self).getAircraftState(aircraft,
1952 timestamp,
1953 data)
1954 state.antiCollisionLightsOn = \
1955 state.antiCollisionLightsOn or state.strobeLightsOn
1956 state.cog = (state.cog / 0.0254 + 21.504) / 94.512
1957
1958 # It seems that N1 does not always go down to 0 properly
1959 # (maybe due to winds?)
1960 state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
1961
1962 state.spoilersExtension = \
1963 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1964
1965 return state
1966
1967#------------------------------------------------------------------------------
1968
1969class CRJ2Model(GenericAircraftModel):
1970 """Generic model for the Bombardier CRJ-200 aircraft."""
1971 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1972
1973 def __init__(self):
1974 """Construct the model."""
1975 super(CRJ2Model, self). \
1976 __init__(flapsNotches = [0, 8, 20, 30, 45],
1977 fuelTanks = CRJ2Model.fuelTanks,
1978 numEngines = 2)
1979
1980 @property
1981 def name(self):
1982 """Get the name for this aircraft model."""
1983 return "X-Plane/Generic Bombardier CRJ-200"
1984
1985#------------------------------------------------------------------------------
1986
1987class F70Model(GenericAircraftModel):
1988 """Generic model for the Fokker F70 aircraft."""
1989 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1990
1991 def __init__(self):
1992 """Construct the model."""
1993 super(F70Model, self). \
1994 __init__(flapsNotches = [0, 8, 15, 25, 42],
1995 fuelTanks = F70Model.fuelTanks,
1996 numEngines = 2)
1997
1998 @property
1999 def name(self):
2000 """Get the name for this aircraft model."""
2001 return "X-Plane/Generic Fokker 70"
2002
2003#------------------------------------------------------------------------------
2004
2005class DC3Model(GenericAircraftModel):
2006 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
2007 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
2008 const.FUELTANK_RIGHT]
2009 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
2010 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
2011
2012 def __init__(self):
2013 """Construct the model."""
2014 super(DC3Model, self). \
2015 __init__(flapsNotches = [0, 15, 30, 45],
2016 fuelTanks = DC3Model.fuelTanks,
2017 numEngines = 2, isN1 = False)
2018 self._leftLevel = 0.0
2019 self._rightLevel = 0.0
2020
2021 @property
2022 def name(self):
2023 """Get the name for this aircraft model."""
2024 return "X-Plane/Generic Lisunov Li-2 (DC-3)"
2025
2026#------------------------------------------------------------------------------
2027
2028class T134Model(GenericAircraftModel):
2029 """Generic model for the Tupolev Tu-134 aircraft."""
2030 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
2031 const.FUELTANK_LEFT_AUX,
2032 const.FUELTANK_CENTRE,
2033 const.FUELTANK_RIGHT_AUX,
2034 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
2035
2036 def __init__(self):
2037 """Construct the model."""
2038 super(T134Model, self). \
2039 __init__(flapsNotches = [0, 10, 20, 30],
2040 fuelTanks = T134Model.fuelTanks,
2041 numEngines = 2)
2042
2043 @property
2044 def name(self):
2045 """Get the name for this aircraft model."""
2046 return "X-Plane/Generic Tupolev Tu-134"
2047
2048#------------------------------------------------------------------------------
2049
2050class T154Model(GenericAircraftModel):
2051 """Generic model for the Tupolev Tu-154 aircraft."""
2052 fuelTanks = [const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
2053 const.FUELTANK_RIGHT, const.FUELTANK_LEFT,
2054 const.FUELTANK_RIGHT_AUX, const.FUELTANK_LEFT_AUX]
2055
2056 def __init__(self):
2057 """Construct the model."""
2058 super(T154Model, self). \
2059 __init__(flapsNotches = [0, 15, 28, 45],
2060 fuelTanks = T154Model.fuelTanks,
2061 numEngines = 3)
2062
2063 @property
2064 def name(self):
2065 """Get the name for this aircraft model."""
2066 return "X-Plane/Generic Tupolev Tu-154"
2067
2068 def getAircraftState(self, aircraft, timestamp, data):
2069 """Get an aircraft state object for the given monitoring data.
2070
2071 This removes the reverser value for the middle engine."""
2072 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
2073 del state.reverser[1]
2074 return state
2075
2076#------------------------------------------------------------------------------
2077
2078class FelisT154Model(T154Model):
2079 """Model for Felis' Tupolev Tu-154-M aircraft."""
2080 @staticmethod
2081 def doesHandle(aircraft, data):
2082 """Determine if this model handler handles the aircraft with the given
2083 name."""
2084 (tailnum, author, description, notes, icao, liveryPath) = data
2085 return aircraft.type==const.AIRCRAFT_T154 and \
2086 author.find("Felis")!=-1 and \
2087 description.find("Tu154M")!=-1
2088
2089 def __init__(self):
2090 """Construct the model."""
2091 super(T154Model, self). \
2092 __init__(flapsNotches = [0, 15, 28, 36, 45],
2093 fuelTanks = T154Model.fuelTanks,
2094 numEngines = 3)
2095
2096 @property
2097 def name(self):
2098 """Get the name for this aircraft model."""
2099 return "X-Plane/Felis Tupolev Tu-154-M"
2100
2101#------------------------------------------------------------------------------
2102
2103class YK40Model(GenericAircraftModel):
2104 """Generic model for the Yakovlev Yak-40 aircraft."""
2105 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
2106
2107 def __init__(self):
2108 """Construct the model."""
2109 super(YK40Model, self). \
2110 __init__(flapsNotches = [0, 20, 35],
2111 fuelTanks = YK40Model.fuelTanks,
2112 numEngines = 2)
2113
2114 @property
2115 def name(self):
2116 """Get the name for this aircraft model."""
2117 return "X-Plane/Generic Yakovlev Yak-40"
2118
2119#------------------------------------------------------------------------------
2120
2121_genericModels = { const.AIRCRAFT_B736 : B737Model,
2122 const.AIRCRAFT_B737 : B737Model,
2123 const.AIRCRAFT_B738 : B737Model,
2124 const.AIRCRAFT_B738C : B737Model,
2125 const.AIRCRAFT_B732 : B737Model,
2126 const.AIRCRAFT_B733 : B737Model,
2127 const.AIRCRAFT_B734 : B737Model,
2128 const.AIRCRAFT_B735 : B737Model,
2129 const.AIRCRAFT_DH8D : DH8DModel,
2130 const.AIRCRAFT_B762 : B767Model,
2131 const.AIRCRAFT_B763 : B767Model,
2132 const.AIRCRAFT_CRJ2 : CRJ2Model,
2133 const.AIRCRAFT_F70 : F70Model,
2134 const.AIRCRAFT_DC3 : DC3Model,
2135 const.AIRCRAFT_T134 : T134Model,
2136 const.AIRCRAFT_T154 : T154Model,
2137 const.AIRCRAFT_YK40 : YK40Model }
2138
2139#------------------------------------------------------------------------------
2140
2141AircraftModel.registerSpecial(ZiboB738Model)
2142AircraftModel.registerSpecial(LevelUpB736Model)
2143AircraftModel.registerSpecial(LevelUpB737Model)
2144AircraftModel.registerSpecial(LevelUpB738Model)
2145AircraftModel.registerSpecial(FJSDH8DModel)
2146AircraftModel.registerSpecial(FelisT154Model)
2147
2148#------------------------------------------------------------------------------
2149
2150# if __name__ == "__main__":
2151# class ConnectionListener:
2152# def connected(self, fsType, descriptor):
2153# """Called when a connection has been established to the flight
2154# simulator of the given type."""
2155# print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
2156
2157# def connectionFailed(self):
2158# """Called when the connection could not be established."""
2159# print "fs.ConnectionListener.connectionFailed"
2160
2161# def disconnected(self):
2162# """Called when a connection to the flight simulator has been broken."""
2163# print "fs.ConnectionListener.disconnected"
2164
2165# class Config:
2166# def __init__(self):
2167# self.onlineACARS = False
2168# self.realIASSmoothingLength = 2
2169# self.realVSSmoothingLength = 2
2170# self.enableSounds = False
2171# self.usingFS2Crew = False
2172
2173# def isMessageTypeFS(self, type):
2174# return True
2175
2176
2177
2178# class GUI:
2179# def __init__(self):
2180# self.config = Config()
2181# self.entranceExam = False
2182# self.zfw = 30000.0
2183
2184# def resetFlightStatus(self):
2185# pass
2186
2187# def setRating(self, value):
2188# pass
2189
2190# def insertFlightLogLine(self, index, ts, text, isFault):
2191# pass
2192
2193# def setStage(self, stage):
2194# pass
2195
2196
2197# from i18n import setLanguage
2198
2199# setLanguage("/home/vi/munka/repules/mlx", "en")
2200
2201# from logger import Logger
2202# from flight import Flight
2203# from acft import DH8D
2204
2205# gui = GUI()
2206
2207# logger = Logger(gui)
2208
2209# flight = Flight(logger, gui)
2210# acft = DH8D(flight)
2211
2212# Watchdog()
2213
2214# connectionListener = ConnectionListener()
2215# simulator = Simulator(connectionListener, connectAttempts = 3)
2216
2217# simulator.connect(acft)
2218
2219# time.sleep(2)
2220
2221# simulator.startMonitoring()
2222
2223# simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
2224
2225# time.sleep(4)
2226
2227# simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116, R117, R210, R211, R212, R212A, R220, R221, R222, R223, R224, R225, R226, R227, R270, R271, R272, R274, R275, R276, R277, R278, R278A, R279", duration = 20)
2228# #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116", duration = 20)
2229# #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115", duration = 20)
2230
2231# time.sleep(30)
2232
2233# simulator.sendMessage("[MLX] Hello", duration = 3)
2234
2235# time.sleep(10)
Note: See TracBrowser for help on using the repository browser.