source: src/mlx/xplane.py@ 1054:a8ec882a1f9d

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

Fix to handle the time going backwards in X-Plane

File size: 84.7 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 diff = self._lastZuluSeconds - zuluSeconds
949 print("xplane.Simulator._getTimestamp: Zulu seconds have gone backwards: %f -> %f, diff: %f" % \
950 (self._lastZuluSeconds, zuluSeconds, diff))
951 if diff>23*60*60:
952 self._timestampDaysOffset += 1
953 else:
954 zuluSeconds = self._lastZuluSeconds
955
956 self._lastZuluSeconds = zuluSeconds
957
958 timestamp = self._timestampBase
959 timestamp += self._timestampDaysOffset * 24 * 3600
960 timestamp += zuluSeconds
961
962 return timestamp
963
964 def _startDefaultNormal(self):
965 """Start the default normal periodic request."""
966 assert self._normalRequestID is None
967 self._timestampBase = None
968 self._normalRequestID = \
969 self._handler.requestPeriodicRead(1.0,
970 Simulator.normalData,
971 self._handleNormal)
972
973 def _stopNormal(self):
974 """Stop the normal period request."""
975 assert self._normalRequestID is not None
976 self._handler.clearPeriodic(self._normalRequestID)
977 self._normalRequestID = None
978 self._monitoring = False
979
980 def _handleNormal(self, data, extra):
981 """Handle the reply to the normal request.
982
983 At the beginning the result consists the data for normalData. When
984 monitoring is started, it contains the result also for the
985 aircraft-specific values.
986 """
987 timestamp = self._getTimestamp(data)
988
989 createdNewModel = self._setAircraftName(timestamp,
990 data.getString(2),
991 data.getString(3),
992 data.getString(4),
993 data.getString(5),
994 data.getString(6),
995 data.getString(7))
996 if self._fuelCallback is not None:
997 self._aircraftModel.getFuel(self._handler, self._fuelCallback)
998 self._fuelCallback = None
999
1000 if self._monitoringRequested and not self._monitoring:
1001 self._stopNormal()
1002 self._startMonitoring()
1003 elif self._monitoring and not self._monitoringRequested:
1004 self._stopNormal()
1005 self._startDefaultNormal()
1006 elif self._monitoring and self._aircraftModel is not None and \
1007 not createdNewModel:
1008 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
1009 timestamp, data)
1010
1011 self._aircraft.handleState(aircraftState)
1012
1013 def _setAircraftName(self, timestamp, tailnum, author, description,
1014 notes, icao, liveryPath):
1015 """Set the name of the aicraft and if it is different from the
1016 previous, create a new model for it.
1017
1018 If so, also notifty the aircraft about the change.
1019
1020 Return if a new model was created."""
1021 aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
1022 if aircraftInfo==self._aircraftInfo:
1023 return False
1024
1025 print("xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
1026 (tailnum, author, description, notes, icao, liveryPath))
1027
1028 self._aircraftInfo = aircraftInfo
1029 needNew = self._aircraftModel is None
1030 needNew = needNew or\
1031 not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
1032 if not needNew:
1033 specialModel = AircraftModel.findSpecial(self._aircraft,
1034 aircraftInfo)
1035 needNew = specialModel is not None and \
1036 specialModel is not self._aircraftModel.__class__
1037
1038 if needNew:
1039 self._setAircraftModel(AircraftModel.create(self._aircraft,
1040 aircraftInfo))
1041
1042 self._aircraft.modelChanged(timestamp, description,
1043 self._aircraftModel.name)
1044
1045 return needNew
1046
1047 def _setAircraftModel(self, model):
1048 """Set a new aircraft model.
1049
1050 It will be queried for the data to monitor and the monitoring request
1051 will be replaced by a new one."""
1052 self._aircraftModel = model
1053 model.simulator = self
1054
1055 if self._monitoring:
1056 self._stopNormal()
1057 self._startMonitoring()
1058
1059 def _startMonitoring(self):
1060 """Start monitoring with the current aircraft model."""
1061 data = Simulator.normalData[:]
1062 self._aircraftModel.addMonitoringData(data, self._fsType)
1063
1064 self._normalRequestID = \
1065 self._handler.requestPeriodicRead(1.0, data,
1066 self._handleNormal)
1067 self._monitoring = True
1068
1069 def _addFlareRate(self, data):
1070 """Append a flare rate to the list of last rates."""
1071 if len(self._flareRates)>=3:
1072 del self._flareRates[0]
1073 self._flareRates.append(data)
1074
1075 def _handleFlare1(self, data, normal):
1076 """Handle the first stage of flare monitoring."""
1077 #self._aircraft.logger.debug("handleFlare1: " + str(data))
1078 if data[1]<=50.0*0.3048:
1079 self._flareStart = time.time()
1080 self._flareStartFS = data[0]
1081 self._handler.clearPeriodic(self._flareRequestID)
1082 self._handler.requestRead(Simulator.flareStartData,
1083 self._handleFlareStart)
1084
1085 self._addFlareRate(data[2])
1086
1087 def _handleFlareStart(self, data, extra):
1088 """Handle the data need to notify the aircraft about the starting of
1089 the flare."""
1090 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
1091 if data is not None:
1092 windDirection = data[1]
1093 if windDirection<0.0: windDirection += 360.0
1094 self._aircraft.flareStarted(data[0], windDirection, data[2],
1095 self._flareStart, self._flareStartFS)
1096
1097 self._flareRequestID = \
1098 self._handler.requestPeriodicRead(0.1,
1099 Simulator.flareData2,
1100 self._handleFlare2)
1101
1102 def _handleFlare2(self, data, normal):
1103 """Handle the first stage of flare monitoring."""
1104 #self._aircraft.logger.debug("handleFlare2: " + str(data))
1105 if data[1]!=0:
1106 flareEnd = time.time()
1107 self._handler.clearPeriodic(self._flareRequestID)
1108 self._flareRequestID = None
1109
1110 flareEndFS = data[0]
1111 if flareEndFS<self._flareStartFS:
1112 flareEndFS += 86400.0
1113
1114 tdRate = min(self._flareRates)
1115 tdRateCalculatedByFS = False
1116
1117 heading = data[6]
1118 if heading<0.0: heading += 360.0
1119
1120 self._aircraft.flareFinished(flareEnd, flareEndFS,
1121 tdRate, tdRateCalculatedByFS,
1122 data[3], data[4], data[5], heading)
1123 else:
1124 self._addFlareRate(data[2])
1125
1126 def _handleZFW(self, data, callback):
1127 """Callback for a ZFW retrieval request."""
1128 zfw = data[0] + data[1]
1129 callback(zfw)
1130
1131 def _handleTime(self, data, callback):
1132 """Callback for a time retrieval request."""
1133 callback(self._getTimestamp(data))
1134
1135 def _handleWeights(self, data, callback):
1136 """Callback for the weights retrieval request."""
1137 dow = data[0]
1138 payload = data[1]
1139 zfw = dow + payload
1140 grossWeight = data[2]
1141 callback(dow, payload, zfw, grossWeight)
1142
1143 def _handleMessageSent(self, success, disconnect):
1144 """Callback for a message sending request."""
1145 #print "xplra.Simulator._handleMessageSent", disconnect
1146 if disconnect:
1147 self._handler.disconnect()
1148
1149 def _handleHotkeysRegistered(self, success, hotkeySet):
1150 """Handle the result of the hotkeys having been written."""
1151 (id, generation) = hotkeySet
1152 with self._hotkeyLock:
1153 if success and id==self._hotkeySetID and \
1154 generation==self._hotkeySetGeneration:
1155 self._hotkeyRequestID = \
1156 self._handler.requestHotkeysState(0.5,
1157 self._handleHotkeys,
1158 hotkeySet)
1159
1160 def _handleHotkeys(self, data, hotkeySet):
1161 """Handle the hotkeys."""
1162 (id, generation) = hotkeySet
1163 with self._hotkeyLock:
1164 if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
1165 return
1166
1167 callback = self._hotkeyCallback
1168 offsets = self._hotkeyOffets
1169
1170 hotkeysPressed = []
1171 for i in range(0, len(data)):
1172 if data[i]:
1173 hotkeysPressed.append(i)
1174
1175 if hotkeysPressed:
1176 callback(id, hotkeysPressed)
1177
1178 def _clearHotkeyRequest(self):
1179 """Clear the hotkey request in the handler if there is any."""
1180 if self._hotkeyRequestID is not None:
1181 self._handler.unregisterHotkeys(self._hotkeysUnregistered)
1182 self._handler.clearPeriodic(self._hotkeyRequestID)
1183 self._hotkeyRequestID = None
1184
1185 def _hotkeysUnregistered(self, result, extra):
1186 """Called when the hotkeys have been unregistered."""
1187 pass
1188
1189#------------------------------------------------------------------------------
1190
1191class AircraftModel(object):
1192 """Base class for the aircraft models.
1193
1194 Aircraft models handle the data arriving from X-Plane and turn it into an
1195 object describing the aircraft's state."""
1196 monitoringData = [ ("paused",
1197 "sim/time/paused", TYPE_INT),
1198 ("latitude",
1199 "sim/flightmodel/position/latitude", TYPE_DOUBLE),
1200 ("longitude",
1201 "sim/flightmodel/position/longitude", TYPE_DOUBLE),
1202 ("replay",
1203 "sim/operation/prefs/replay_mode", TYPE_INT),
1204 ("overspeed",
1205 "sim/flightmodel/failures/over_vne", TYPE_INT),
1206 ("stalled",
1207 "sim/flightmodel/failures/stallwarning", TYPE_INT),
1208 ("onTheGround",
1209 "sim/flightmodel/failures/onground_any", TYPE_INT),
1210 ("emptyWeight",
1211 "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
1212 ("payloadWeight",
1213 "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
1214 ("grossWeight",
1215 "sim/flightmodel/weight/m_total", TYPE_FLOAT),
1216 ("heading",
1217 "sim/flightmodel/position/psi", TYPE_FLOAT),
1218 ("pitch",
1219 "sim/flightmodel/position/theta", TYPE_FLOAT),
1220 ("bank",
1221 "sim/flightmodel/position/phi", TYPE_FLOAT),
1222 ("ias",
1223 "sim/flightmodel/position/indicated_airspeed2",
1224 TYPE_FLOAT),
1225 ("mach",
1226 "sim/flightmodel/misc/machno", TYPE_FLOAT),
1227 ("groundSpeed",
1228 "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
1229 ("vs",
1230 "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
1231 ("radioAltitude",
1232 "sim/flightmodel/position/y_agl", TYPE_FLOAT),
1233 ("altitude",
1234 "sim/flightmodel/position/elevation", TYPE_FLOAT),
1235 ("gLoad",
1236 "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
1237 ("flapsControl",
1238 "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
1239 ("flapsLeft",
1240 "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
1241 ("flapsRight",
1242 "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
1243 ("navLights",
1244 "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
1245 ("beaconLights",
1246 "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
1247 ("strobeLights",
1248 "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
1249 ("landingLights",
1250 "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
1251 ("pitot",
1252 "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
1253 ("parking",
1254 "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
1255 ("gearControl",
1256 "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
1257 ("noseGear",
1258 "sim/flightmodel2/gear/deploy_ratio",
1259 (TYPE_FLOAT_ARRAY, 1)),
1260 ("spoilers",
1261 "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
1262 ("altimeter",
1263 "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
1264 ("qnh",
1265 "sim/physics/earth_pressure_p", TYPE_FLOAT),
1266 ("nav1",
1267 "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
1268 ("nav1_obs",
1269 "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
1270 ("nav2",
1271 "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
1272 ("nav2_obs",
1273 "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
1274 ("adf1",
1275 "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
1276 ("adf2",
1277 "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
1278 ("squawk",
1279 "sim/cockpit/radios/transponder_code", TYPE_INT),
1280 ("windSpeed",
1281 "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
1282 ("windDirection",
1283 "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
1284 ("visibility",
1285 "sim/weather/visibility_reported_m", TYPE_FLOAT),
1286 ("cog",
1287 "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
1288 ("xpdrC",
1289 "sim/cockpit/radios/transponder_mode", TYPE_INT),
1290 ("apMaster",
1291 "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
1292 ("apState",
1293 "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
1294 ("apHeading",
1295 "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
1296 ("apAltitude",
1297 "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
1298 ("elevatorTrim",
1299 "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
1300 ("antiIceOn",
1301 "sim/cockpit/switches/anti_ice_on", TYPE_INT),
1302 ("surfaceHeat",
1303 "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
1304 ("propHeat",
1305 "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
1306 ("autopilotOn",
1307 "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
1308 ("apHeadingMode",
1309 "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
1310
1311
1312 specialModels = []
1313
1314 @staticmethod
1315 def registerSpecial(clazz):
1316 """Register the given class as a special model."""
1317 AircraftModel.specialModels.append(clazz)
1318
1319 @staticmethod
1320 def findSpecial(aircraft, aircraftInfo):
1321 for specialModel in AircraftModel.specialModels:
1322 if specialModel.doesHandle(aircraft, aircraftInfo):
1323 return specialModel
1324 return None
1325
1326 @staticmethod
1327 def create(aircraft, aircraftInfo):
1328 """Create the model for the given aircraft name, and notify the
1329 aircraft about it."""
1330 specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
1331 if specialModel is not None:
1332 return specialModel()
1333 if aircraft.type in _genericModels:
1334 return _genericModels[aircraft.type]()
1335 else:
1336 return GenericModel()
1337
1338 @staticmethod
1339 def _convertFrequency(value):
1340 """Convert the given frequency value into a string."""
1341 return "%.2f" % (value/100.0,)
1342
1343 @staticmethod
1344 def _convertOBS(value):
1345 """Convert the given OBS value into an integer."""
1346 while value<0.0:
1347 value += 360.0
1348 return int(round(value))
1349
1350 def __init__(self, flapsNotches):
1351 """Construct the aircraft model.
1352
1353 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1354 self._flapsNotches = flapsNotches
1355 self._simulator = None
1356
1357 @property
1358 def name(self):
1359 """Get the name for this aircraft model."""
1360 return "X-Plane/Generic"
1361
1362 @property
1363 def simulator(self):
1364 """Get the simulator this aircraft model works for."""
1365 return self._simulator
1366
1367 @simulator.setter
1368 def simulator(self, simulator):
1369 """Get the simulator this aircraft model works for."""
1370 self._simulator = simulator
1371
1372 def doesHandle(self, aircraft, aircraftInfo):
1373 """Determine if the model handles the given aircraft name.
1374
1375 This default implementation returns False."""
1376 return False
1377
1378 def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
1379 """Add the given X-Plane dataref name and type to the given array and a
1380 member attribute with the given name."""
1381 dest.append((name, type))
1382 if attrName is not None:
1383 setattr(self, attrName, len(dest)-1)
1384
1385 def _addDataWithIndexMembers(self, dest, prefix, data):
1386 """Add X-Plane dataref data to the given array and also corresponding
1387 index member variables with the given prefix.
1388
1389 data is a list of triplets of the following items:
1390 - the name of the data item. The index member variable will have a name
1391 created by prepending the given prefix to this name.
1392 - the X-Plane dataref name
1393 - the dataref type
1394
1395 The latter two items will be appended to dest."""
1396 for (name, datarefName, type) in data:
1397 self._addDatarefWithIndexMember(dest, datarefName, type,
1398 prefix + name)
1399
1400 def addMonitoringData(self, data, fsType):
1401 """Add the model-specific monitoring data to the given array."""
1402 self._addDataWithIndexMembers(data, "_monidx_",
1403 AircraftModel.monitoringData)
1404
1405 def getAircraftState(self, aircraft, timestamp, data):
1406 """Get an aircraft state object for the given monitoring data."""
1407 state = fs.AircraftState()
1408
1409 lnavOn = data[self._monidx_autopilotOn]!=0 and \
1410 data[self._monidx_apHeadingMode]==2
1411
1412 state.timestamp = timestamp
1413
1414 state.latitude = data[self._monidx_latitude]
1415 state.longitude = data[self._monidx_longitude]
1416 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1417
1418 state.paused = data[self._monidx_paused]!=0 or \
1419 data[self._monidx_replay]!=0
1420 state.trickMode = data[self._monidx_replay]!=0
1421
1422 state.overspeed = data[self._monidx_overspeed]!=0
1423 state.stalled = data[self._monidx_stalled]!=0
1424 state.onTheGround = data[self._monidx_onTheGround]!=0
1425
1426 state.zfw = data[self._monidx_emptyWeight] + \
1427 data[self._monidx_payloadWeight]
1428 state.grossWeight = data[self._monidx_grossWeight]
1429
1430 state.heading = data[self._monidx_heading]
1431
1432 state.pitch = -1.0 * data[self._monidx_pitch]
1433 state.bank = data[self._monidx_bank]
1434
1435 state.ias = data[self._monidx_ias]
1436 state.mach = data[self._monidx_mach]
1437 state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
1438 state.vs = data[self._monidx_vs]
1439
1440 state.radioAltitude = data[self._monidx_radioAltitude]/.3048
1441 state.altitude = data[self._monidx_altitude]/.3048
1442
1443 state.gLoad = data[self._monidx_gLoad]
1444
1445 flapsControl = data[self._monidx_flapsControl]
1446 flapsIndex = int(round(flapsControl * (len(self._flapsNotches)-1)))
1447 state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
1448
1449 state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
1450
1451 state.navLightsOn = data[self._monidx_navLights] != 0
1452 state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
1453 state.landingLightsOn = data[self._monidx_landingLights] != 0
1454 state.strobeLightsOn = data[self._monidx_strobeLights] != 0
1455
1456 state.pitotHeatOn = data[self._monidx_pitot]!=0
1457
1458 state.parking = data[self._monidx_parking]>=0.5
1459
1460 state.gearControlDown = data[self._monidx_gearControl]!=0
1461 state.gearsDown = data[self._monidx_noseGear][0]>0.99
1462
1463 state.spoilersArmed = None
1464
1465 state.spoilersExtension = data[self._monidx_spoilers]*100.0
1466
1467 state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
1468 state.altimeterReliable = True
1469 state.qnh = data[self._monidx_qnh]/100.0
1470
1471 state.ils = None
1472 state.ils_obs = None
1473 state.ils_manual = False
1474 state.nav1 = self._convertFrequency(data[self._monidx_nav1])
1475 state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
1476 state.nav1_manual = True
1477 state.nav2 = self._convertFrequency(data[self._monidx_nav2])
1478 state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
1479 state.nav2_manual = not lnavOn
1480 state.adf1 = str(data[self._monidx_adf1])
1481 state.adf2 = str(data[self._monidx_adf2])
1482
1483 state.squawk = "%04d" % (data[self._monidx_squawk],)
1484
1485 state.windSpeed = data[self._monidx_windSpeed]
1486 state.windDirection = data[self._monidx_windDirection]
1487 if state.windDirection<0.0: state.windDirection += 360.0
1488
1489 state.visibility = data[self._monidx_visibility]
1490
1491 state.cog = data[self._monidx_cog]
1492
1493 state.xpdrC = data[self._monidx_xpdrC]==2
1494 state.autoXPDR = False
1495
1496 state.apMaster = data[self._monidx_apMaster]==2
1497 apState = data[self._monidx_apState]
1498 if lnavOn:
1499 state.apHeadingHold = None
1500 state.apHeading = None
1501 else:
1502 state.apHeadingHold = (apState&0x00002)!=0
1503 state.apHeading = data[self._monidx_apHeading]
1504
1505 state.apAltitudeHold = (apState&0x04000)!=0
1506 state.apAltitude = data[self._monidx_apAltitude]
1507
1508 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1509
1510 state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
1511 data[self._monidx_surfaceHeat]!=0 or \
1512 data[self._monidx_propHeat]!=0
1513
1514 return state
1515
1516#------------------------------------------------------------------------------
1517
1518class GenericAircraftModel(AircraftModel):
1519 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1520 values and some other common parameters in a generic way."""
1521
1522 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1523 """Construct the generic aircraft model with the given data.
1524
1525 flapsNotches is an array of how much degrees the individual flaps
1526 notches mean.
1527
1528 fuelTanks is an array of const.FUELTANK_XXX constants about the
1529 aircraft's fuel tanks. They will be converted to offsets.
1530
1531 numEngines is the number of engines the aircraft has.
1532
1533 isN1 determines if the engines have an N1 value or an RPM value
1534 (e.g. pistons)."""
1535 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1536
1537 self._fuelTanks = fuelTanks
1538 self._fuelTankCapacities = [1.0] * len(fuelTanks)
1539 self._fuelIndex = None
1540 self._numEngines = numEngines
1541 self._engineStartIndex = None
1542 self._isN1 = isN1
1543
1544 def doesHandle(self, aircraft, aircraftInfo):
1545 """Determine if the model handles the given aircraft name.
1546
1547 This implementation returns True."""
1548 return True
1549
1550 def addMonitoringData(self, data, fsType):
1551 """Add the model-specific monitoring data to the given array."""
1552 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
1553
1554 self._fuelIndex = self._addFuelData(data)
1555
1556 self._engineStartIndex = len(data)
1557 if self._isN1:
1558 self._addDatarefWithIndexMember(data,
1559 "sim/flightmodel/engine/ENGN_N1_",
1560 (TYPE_FLOAT_ARRAY,
1561 self._numEngines))
1562 else:
1563 self._addDatarefWithIndexMember(data,
1564 "sim/flightmodel/engine/POINT_tacrad",
1565 (TYPE_FLOAT_ARRAY,
1566 self._numEngines))
1567
1568 self._addDatarefWithIndexMember(data,
1569 "sim/flightmodel/engine/ENGN_propmode",
1570 (TYPE_INT_ARRAY, self._numEngines))
1571
1572 def getAircraftState(self, aircraft, timestamp, data):
1573 """Get the aircraft state.
1574
1575 Get it from the parent, and then add the data about the fuel levels and
1576 the engine parameters."""
1577 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1578 timestamp,
1579 data)
1580
1581 state.fuel = []
1582 state.totalFuel = 0.0
1583
1584 fuelAmounts = data[self._fuelIndex]
1585 for i in range(0, len(self._fuelTanks)):
1586 amount = fuelAmounts[i]
1587 state.fuel.append((self._fuelTanks[i], amount))
1588 state.totalFuel += amount
1589
1590 power = data[self._engineStartIndex]
1591
1592 state.n1 = power[:] if self._isN1 else None
1593 state.rpm = None if self._isN1 else power[:]
1594
1595 propMode = data[self._engineStartIndex+1]
1596 state.reverser = [mode == 3 for mode in propMode]
1597
1598 return state
1599
1600 def getFuel(self, handler, callback):
1601 """Get the fuel information for this model.
1602
1603 See Simulator.getFuel for more information. This
1604 implementation simply queries the fuel tanks given to the
1605 constructor."""
1606 data = []
1607 self._addFuelData(data)
1608 data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
1609 data.append( ("sim/aircraft/overflow/acf_tank_rat",
1610 (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
1611
1612 handler.requestRead(data, self._handleFuelRetrieved,
1613 extra = callback)
1614
1615 def setFuelLevel(self, handler, levels):
1616 """Set the fuel level.
1617
1618 See the description of Simulator.setFuelLevel. This
1619 implementation simply sets the fuel tanks as given."""
1620 data = []
1621 for (tank, level) in levels:
1622 try:
1623 index = self._fuelTanks.index(tank)
1624 data.append( ("sim/flightmodel/weight/m_fuel",
1625 (TYPE_FLOAT_ARRAY, 1, index),
1626 [level * self._fuelTankCapacities[index]]) )
1627 except:
1628 print("xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
1629 (tank,))
1630
1631 handler.requestWrite(data, self._handleFuelWritten)
1632
1633 def _addFuelData(self, data):
1634 """Add the fuel offsets to the given data array.
1635
1636 Returns the index of the first fuel tank's data."""
1637 fuelStartIndex = len(data)
1638 data.append( ("sim/flightmodel/weight/m_fuel",
1639 (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
1640
1641 return fuelStartIndex
1642
1643 def _convertFuelData(self, data, index = 0, addCapacities = False):
1644 """Convert the given data into a fuel info list.
1645
1646 The list consists of two or three-tuples of the following
1647 items:
1648 - the fuel tank ID,
1649 - the amount of the fuel in kg,
1650 - if addCapacities is True, the total capacity of the tank."""
1651 fuelWeight = data[index] / 256.0
1652 index += 1
1653
1654 result = []
1655 totalFuel = 0
1656 for fuelTank in self._fuelTanks:
1657 capacity = data[index+1] * fuelWeight * const.LBSTOKG
1658 if capacity>=1.0:
1659 amount = data[index] * capacity / 128.0 / 65536.0
1660
1661 result.append( (fuelTank, amount, capacity) if addCapacities
1662 else (fuelTank, amount))
1663 totalFuel += amount
1664 index += 2
1665
1666 return (result, totalFuel)
1667
1668 def _handleFuelRetrieved(self, data, callback):
1669 """Callback for a fuel retrieval request."""
1670 result = []
1671 totalCapacity = data[1]
1672 for index in range(0, len(self._fuelTanks)):
1673 amount = data[0][index]
1674 capacity = data[2][index] * totalCapacity
1675 self._fuelTankCapacities[index] = capacity
1676 result.append( (self._fuelTanks[index], amount, capacity) )
1677
1678 callback(result)
1679
1680 def _handleFuelWritten(self, success, extra):
1681 """Callback for a fuel setting request."""
1682 pass
1683
1684#------------------------------------------------------------------------------
1685
1686class GenericModel(GenericAircraftModel):
1687 """Generic aircraft model for an unknown type."""
1688 def __init__(self):
1689 """Construct the model."""
1690 super(GenericModel, self). \
1691 __init__(flapsNotches = [0, 10, 20, 30],
1692 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1693 numEngines = 2)
1694
1695 @property
1696 def name(self):
1697 """Get the name for this aircraft model."""
1698 return "X-Plane/Generic"
1699
1700#------------------------------------------------------------------------------
1701
1702class B737Model(GenericAircraftModel):
1703 """Generic model for the Boeing 737 Classing and NG aircraft."""
1704 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1705
1706 def __init__(self):
1707 """Construct the model."""
1708 super(B737Model, self). \
1709 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1710 fuelTanks = B737Model.fuelTanks,
1711 numEngines = 2)
1712
1713 @property
1714 def name(self):
1715 """Get the name for this aircraft model."""
1716 return "X-Plane/Generic Boeing 737"
1717
1718#------------------------------------------------------------------------------
1719
1720class ZiboB737NGModel(B737Model):
1721 """Base model for the Zibo and LevelUp Boeing 737 models."""
1722 def __init__(self, flapsRatios = [0.0, 0.081633, 0.142857, 0.224490,
1723 0.285714, 0.367347, 0.551020, 0.714286,
1724 1.0]):
1725 super(ZiboB737NGModel, self).__init__()
1726 self._flapsRatios = flapsRatios
1727
1728 def addMonitoringData(self, data, fsType):
1729 """Add the model-specific monitoring data to the given array."""
1730 super(ZiboB737NGModel, self).addMonitoringData(data, fsType)
1731
1732 self._speedBrakeIndex = len(data)
1733 self._addDatarefWithIndexMember(data,
1734 "sim/flightmodel2/wing/speedbrake1_deg",
1735 (TYPE_FLOAT_ARRAY, 2))
1736 self._addDatarefWithIndexMember(data,
1737 "sim/flightmodel2/wing/speedbrake2_deg",
1738 (TYPE_FLOAT_ARRAY, 2))
1739 self._cgIndex = len(data)
1740 self._addDatarefWithIndexMember(data,
1741 "laminar/B738/tab/cg_pos",
1742 TYPE_FLOAT)
1743 self._wingHeatIndex = len(data)
1744 self._addDatarefWithIndexMember(data,
1745 "laminar/B738/ice/wing_heat_pos",
1746 TYPE_FLOAT)
1747 self._eng1HeatIndex = len(data)
1748 self._addDatarefWithIndexMember(data,
1749 "laminar/B738/ice/eng1_heat_pos",
1750 TYPE_FLOAT)
1751 self._eng2HeatIndex = len(data)
1752 self._addDatarefWithIndexMember(data,
1753 "laminar/B738/ice/eng2_heat_pos",
1754 TYPE_FLOAT)
1755 self._spoilersArmedIndex = len(data)
1756 self._addDatarefWithIndexMember(data,
1757 "laminar/B738/annunciator/speedbrake_armed",
1758 TYPE_FLOAT)
1759
1760
1761 def getAircraftState(self, aircraft, timestamp, data):
1762 """Get the aircraft state."""
1763 state = super(ZiboB737NGModel, self).getAircraftState(aircraft,
1764 timestamp,
1765 data)
1766 state.cog = data[self._cgIndex]/100.0
1767
1768 flapsRatios = self._flapsRatios
1769 flapsRatio = data[self._monidx_flapsLeft]
1770 index = len(flapsRatios)
1771 for i in range(1, len(flapsRatios)):
1772 if flapsRatio<flapsRatios[i]:
1773 index = i-1
1774 break
1775 if index<len(flapsRatios):
1776 flapsRatio0 = flapsRatios[index]
1777 flapsNotch0 = self._flapsNotches[index]
1778 state.flaps = flapsNotch0 + \
1779 (self._flapsNotches[index+1] - flapsNotch0) * \
1780 (flapsRatio - flapsRatio0) / \
1781 (flapsRatios[index+1] - flapsRatio0)
1782 else:
1783 state.flaps = self._flapsNotches[-1]
1784
1785 # 0 -> -1
1786 # 15 -> 0.790881
1787 state.elevatorTrim = \
1788 15.0 * (data[self._monidx_elevatorTrim] + 1) / 1.790881
1789
1790 state.spoilersExtension = \
1791 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1792
1793 state.antiIceOn = data[self._wingHeatIndex]!=0 or \
1794 data[self._eng1HeatIndex]!=0 or \
1795 data[self._eng2HeatIndex]!=0
1796
1797 state.spoilersArmed = data[self._spoilersArmedIndex]!=0
1798
1799 return state
1800
1801#------------------------------------------------------------------------------
1802
1803class ZiboB738Model(ZiboB737NGModel):
1804 """Model for the Zibo Boeing 737-800 model."""
1805 @staticmethod
1806 def doesHandle(aircraft, data):
1807 """Determine if this model handler handles the aircraft with the given
1808 name."""
1809 (tailnum, author, description, notes, icao, liveryPath) = data
1810 return author=="Alex Unruh" and \
1811 description=="Boeing 737-800X" and \
1812 notes.startswith("ZIBOmod") and \
1813 icao=="B738"
1814
1815 def __init__(self):
1816 """Construct the model."""
1817 super(ZiboB738Model, self).__init__(
1818 flapsRatios = [0.0, 0.081633, 0.142857, 0.224490, 0.285714, 0.346939,
1819 0.551020, 0.673469, 1.0])
1820
1821 @property
1822 def name(self):
1823 """Get the name for this aircraft model."""
1824 return "Zibo Boeing 737-800"
1825
1826#------------------------------------------------------------------------------
1827
1828class LevelUpB736Model(ZiboB737NGModel):
1829 """Model for the LevelUp Boeing 737-600 model."""
1830
1831 @staticmethod
1832 def doesHandle(aircraft, data):
1833 """Determine if this model handler handles the aircraft with the given
1834 name."""
1835 (tailnum, author, description, notes, icao, liveryPath) = data
1836 return author=="Alex Unruh" and \
1837 description=="Boeing 737-600NG" and \
1838 icao=="B736"
1839
1840 @property
1841 def name(self):
1842 """Get the name for this aircraft model."""
1843 return "LevelUp Boeing 737-600"
1844
1845#------------------------------------------------------------------------------
1846
1847class LevelUpB737Model(ZiboB737NGModel):
1848 """Model for the LevelUp Boeing 737-700 model."""
1849
1850 @staticmethod
1851 def doesHandle(aircraft, data):
1852 """Determine if this model handler handles the aircraft with the given
1853 name."""
1854 (tailnum, author, description, notes, icao, liveryPath) = data
1855 return author=="Alex Unruh" and \
1856 description=="Boeing 737-700NG" and \
1857 icao=="B737"
1858
1859 @property
1860 def name(self):
1861 """Get the name for this aircraft model."""
1862 return "LevelUp Boeing 737-700"
1863
1864#------------------------------------------------------------------------------
1865
1866class LevelUpB738Model(ZiboB737NGModel):
1867 """Model for the LevelUp Boeing 737-800 model."""
1868
1869 @staticmethod
1870 def doesHandle(aircraft, data):
1871 """Determine if this model handler handles the aircraft with the given
1872 name."""
1873 (tailnum, author, description, notes, icao, liveryPath) = data
1874 return author=="Alex Unruh" and \
1875 description=="Boeing 737-800NG" and \
1876 icao=="B738"
1877
1878 @property
1879 def name(self):
1880 """Get the name for this aircraft model."""
1881 return "LevelUp Boeing 737-800"
1882
1883#------------------------------------------------------------------------------
1884
1885class B767Model(GenericAircraftModel):
1886 """Generic model for the Boeing 767 aircraft."""
1887 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1888
1889 def __init__(self):
1890 """Construct the model."""
1891 super(B767Model, self). \
1892 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1893 fuelTanks = B767Model.fuelTanks,
1894 numEngines = 2)
1895
1896 @property
1897 def name(self):
1898 """Get the name for this aircraft model."""
1899 return "X-Plane/Generic Boeing 767"
1900
1901#------------------------------------------------------------------------------
1902
1903class DH8DModel(GenericAircraftModel):
1904 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1905 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1906
1907 def __init__(self):
1908 """Construct the model."""
1909 super(DH8DModel, self). \
1910 __init__(flapsNotches = [0, 5, 10, 15, 35],
1911 fuelTanks = DH8DModel.fuelTanks,
1912 numEngines = 2)
1913
1914 @property
1915 def name(self):
1916 """Get the name for this aircraft model."""
1917 return "X-Plane/Generic Bombardier Dash 8-Q400"
1918
1919#------------------------------------------------------------------------------
1920
1921class FJSDH8DModel(DH8DModel):
1922 """Model handler for the FlyJSim Dash 8-Q400."""
1923 @staticmethod
1924 def doesHandle(aircraft, data):
1925 """Determine if this model handler handles the aircraft with the given
1926 name."""
1927 (tailnum, author, description, notes, icao, liveryPath) = data
1928 return aircraft.type==const.AIRCRAFT_DH8D and \
1929 description.find("Dash 8 Q400")!=-1 and \
1930 ((author in ["2012", "2013"] and tailnum=="N62890") or \
1931 author.find("Jack Skieczius")!=-1)
1932
1933 @property
1934 def name(self):
1935 """Get the name for this aircraft model."""
1936 return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
1937
1938 def addMonitoringData(self, data, fsType):
1939 """Add the model-specific monitoring data to the given array."""
1940 super(FJSDH8DModel, self).addMonitoringData(data, fsType)
1941
1942 self._speedBrakeIndex = len(data)
1943 self._addDatarefWithIndexMember(data,
1944 "sim/flightmodel2/wing/speedbrake1_deg",
1945 (TYPE_FLOAT_ARRAY, 2))
1946 self._addDatarefWithIndexMember(data,
1947 "sim/flightmodel2/wing/speedbrake2_deg",
1948 (TYPE_FLOAT_ARRAY, 2))
1949
1950
1951 def getAircraftState(self, aircraft, timestamp, data):
1952 """Get the aircraft state.
1953
1954 Get it from the parent, and then invert the pitot heat state."""
1955 state = super(FJSDH8DModel, self).getAircraftState(aircraft,
1956 timestamp,
1957 data)
1958 state.antiCollisionLightsOn = \
1959 state.antiCollisionLightsOn or state.strobeLightsOn
1960 state.cog = (state.cog / 0.0254 + 21.504) / 94.512
1961
1962 # It seems that N1 does not always go down to 0 properly
1963 # (maybe due to winds?)
1964 state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
1965
1966 state.spoilersExtension = \
1967 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1968
1969 return state
1970
1971#------------------------------------------------------------------------------
1972
1973class CRJ2Model(GenericAircraftModel):
1974 """Generic model for the Bombardier CRJ-200 aircraft."""
1975 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1976
1977 def __init__(self):
1978 """Construct the model."""
1979 super(CRJ2Model, self). \
1980 __init__(flapsNotches = [0, 8, 20, 30, 45],
1981 fuelTanks = CRJ2Model.fuelTanks,
1982 numEngines = 2)
1983
1984 @property
1985 def name(self):
1986 """Get the name for this aircraft model."""
1987 return "X-Plane/Generic Bombardier CRJ-200"
1988
1989#------------------------------------------------------------------------------
1990
1991class F70Model(GenericAircraftModel):
1992 """Generic model for the Fokker F70 aircraft."""
1993 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1994
1995 def __init__(self):
1996 """Construct the model."""
1997 super(F70Model, self). \
1998 __init__(flapsNotches = [0, 8, 15, 25, 42],
1999 fuelTanks = F70Model.fuelTanks,
2000 numEngines = 2)
2001
2002 @property
2003 def name(self):
2004 """Get the name for this aircraft model."""
2005 return "X-Plane/Generic Fokker 70"
2006
2007#------------------------------------------------------------------------------
2008
2009class DC3Model(GenericAircraftModel):
2010 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
2011 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
2012 const.FUELTANK_RIGHT]
2013 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
2014 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
2015
2016 def __init__(self):
2017 """Construct the model."""
2018 super(DC3Model, self). \
2019 __init__(flapsNotches = [0, 15, 30, 45],
2020 fuelTanks = DC3Model.fuelTanks,
2021 numEngines = 2, isN1 = False)
2022 self._leftLevel = 0.0
2023 self._rightLevel = 0.0
2024
2025 @property
2026 def name(self):
2027 """Get the name for this aircraft model."""
2028 return "X-Plane/Generic Lisunov Li-2 (DC-3)"
2029
2030#------------------------------------------------------------------------------
2031
2032class T134Model(GenericAircraftModel):
2033 """Generic model for the Tupolev Tu-134 aircraft."""
2034 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
2035 const.FUELTANK_LEFT_AUX,
2036 const.FUELTANK_CENTRE,
2037 const.FUELTANK_RIGHT_AUX,
2038 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
2039
2040 def __init__(self):
2041 """Construct the model."""
2042 super(T134Model, self). \
2043 __init__(flapsNotches = [0, 10, 20, 30],
2044 fuelTanks = T134Model.fuelTanks,
2045 numEngines = 2)
2046
2047 @property
2048 def name(self):
2049 """Get the name for this aircraft model."""
2050 return "X-Plane/Generic Tupolev Tu-134"
2051
2052#------------------------------------------------------------------------------
2053
2054class T154Model(GenericAircraftModel):
2055 """Generic model for the Tupolev Tu-154 aircraft."""
2056 fuelTanks = [const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
2057 const.FUELTANK_RIGHT, const.FUELTANK_LEFT,
2058 const.FUELTANK_RIGHT_AUX, const.FUELTANK_LEFT_AUX]
2059
2060 def __init__(self):
2061 """Construct the model."""
2062 super(T154Model, self). \
2063 __init__(flapsNotches = [0, 15, 28, 45],
2064 fuelTanks = T154Model.fuelTanks,
2065 numEngines = 3)
2066
2067 @property
2068 def name(self):
2069 """Get the name for this aircraft model."""
2070 return "X-Plane/Generic Tupolev Tu-154"
2071
2072 def getAircraftState(self, aircraft, timestamp, data):
2073 """Get an aircraft state object for the given monitoring data.
2074
2075 This removes the reverser value for the middle engine."""
2076 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
2077 del state.reverser[1]
2078 return state
2079
2080#------------------------------------------------------------------------------
2081
2082class FelisT154Model(T154Model):
2083 """Model for Felis' Tupolev Tu-154-M aircraft."""
2084 @staticmethod
2085 def doesHandle(aircraft, data):
2086 """Determine if this model handler handles the aircraft with the given
2087 name."""
2088 (tailnum, author, description, notes, icao, liveryPath) = data
2089 return aircraft.type==const.AIRCRAFT_T154 and \
2090 author.find("Felis")!=-1 and \
2091 description.find("Tu154M")!=-1
2092
2093 def __init__(self):
2094 """Construct the model."""
2095 super(T154Model, self). \
2096 __init__(flapsNotches = [0, 15, 28, 36, 45],
2097 fuelTanks = T154Model.fuelTanks,
2098 numEngines = 3)
2099
2100 @property
2101 def name(self):
2102 """Get the name for this aircraft model."""
2103 return "X-Plane/Felis Tupolev Tu-154-M"
2104
2105#------------------------------------------------------------------------------
2106
2107class YK40Model(GenericAircraftModel):
2108 """Generic model for the Yakovlev Yak-40 aircraft."""
2109 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
2110
2111 def __init__(self):
2112 """Construct the model."""
2113 super(YK40Model, self). \
2114 __init__(flapsNotches = [0, 20, 35],
2115 fuelTanks = YK40Model.fuelTanks,
2116 numEngines = 2)
2117
2118 @property
2119 def name(self):
2120 """Get the name for this aircraft model."""
2121 return "X-Plane/Generic Yakovlev Yak-40"
2122
2123#------------------------------------------------------------------------------
2124
2125_genericModels = { const.AIRCRAFT_B736 : B737Model,
2126 const.AIRCRAFT_B737 : B737Model,
2127 const.AIRCRAFT_B738 : B737Model,
2128 const.AIRCRAFT_B738C : B737Model,
2129 const.AIRCRAFT_B732 : B737Model,
2130 const.AIRCRAFT_B733 : B737Model,
2131 const.AIRCRAFT_B734 : B737Model,
2132 const.AIRCRAFT_B735 : B737Model,
2133 const.AIRCRAFT_DH8D : DH8DModel,
2134 const.AIRCRAFT_B762 : B767Model,
2135 const.AIRCRAFT_B763 : B767Model,
2136 const.AIRCRAFT_CRJ2 : CRJ2Model,
2137 const.AIRCRAFT_F70 : F70Model,
2138 const.AIRCRAFT_DC3 : DC3Model,
2139 const.AIRCRAFT_T134 : T134Model,
2140 const.AIRCRAFT_T154 : T154Model,
2141 const.AIRCRAFT_YK40 : YK40Model }
2142
2143#------------------------------------------------------------------------------
2144
2145AircraftModel.registerSpecial(ZiboB738Model)
2146AircraftModel.registerSpecial(LevelUpB736Model)
2147AircraftModel.registerSpecial(LevelUpB737Model)
2148AircraftModel.registerSpecial(LevelUpB738Model)
2149AircraftModel.registerSpecial(FJSDH8DModel)
2150AircraftModel.registerSpecial(FelisT154Model)
2151
2152#------------------------------------------------------------------------------
2153
2154# if __name__ == "__main__":
2155# class ConnectionListener:
2156# def connected(self, fsType, descriptor):
2157# """Called when a connection has been established to the flight
2158# simulator of the given type."""
2159# print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
2160
2161# def connectionFailed(self):
2162# """Called when the connection could not be established."""
2163# print "fs.ConnectionListener.connectionFailed"
2164
2165# def disconnected(self):
2166# """Called when a connection to the flight simulator has been broken."""
2167# print "fs.ConnectionListener.disconnected"
2168
2169# class Config:
2170# def __init__(self):
2171# self.onlineACARS = False
2172# self.realIASSmoothingLength = 2
2173# self.realVSSmoothingLength = 2
2174# self.enableSounds = False
2175# self.usingFS2Crew = False
2176
2177# def isMessageTypeFS(self, type):
2178# return True
2179
2180
2181
2182# class GUI:
2183# def __init__(self):
2184# self.config = Config()
2185# self.entranceExam = False
2186# self.zfw = 30000.0
2187
2188# def resetFlightStatus(self):
2189# pass
2190
2191# def setRating(self, value):
2192# pass
2193
2194# def insertFlightLogLine(self, index, ts, text, isFault):
2195# pass
2196
2197# def setStage(self, stage):
2198# pass
2199
2200
2201# from i18n import setLanguage
2202
2203# setLanguage("/home/vi/munka/repules/mlx", "en")
2204
2205# from logger import Logger
2206# from flight import Flight
2207# from acft import DH8D
2208
2209# gui = GUI()
2210
2211# logger = Logger(gui)
2212
2213# flight = Flight(logger, gui)
2214# acft = DH8D(flight)
2215
2216# Watchdog()
2217
2218# connectionListener = ConnectionListener()
2219# simulator = Simulator(connectionListener, connectAttempts = 3)
2220
2221# simulator.connect(acft)
2222
2223# time.sleep(2)
2224
2225# simulator.startMonitoring()
2226
2227# simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
2228
2229# time.sleep(4)
2230
2231# 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)
2232# #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)
2233# #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)
2234
2235# time.sleep(30)
2236
2237# simulator.sendMessage("[MLX] Hello", duration = 3)
2238
2239# time.sleep(10)
Note: See TracBrowser for help on using the repository browser.