source: src/mlx/xplane.py@ 921:11f9fb4fb178

python3
Last change on this file since 921:11f9fb4fb178 was 921:11f9fb4fb178, checked in by István Váradi <ivaradi@…>, 5 years ago

Fixed ordering for classes that used cmp (re #347).

File size: 78.4 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._latin1decoder = codecs.getdecoder("iso-8859-1")
736 self._fuelCallback = None
737
738 def connect(self, aircraft):
739 """Initiate a connection to the simulator."""
740 self._aircraft = aircraft
741 self._aircraftInfo = None
742 self._aircraftModel = None
743 self._handler.connect()
744 if self._normalRequestID is None:
745 self._nextSyncTime = -1
746 self._startDefaultNormal()
747
748 def reconnect(self):
749 """Initiate a reconnection to the simulator.
750
751 It does not reset already set up data, just calls connect() on the
752 handler."""
753 self._handler.connect()
754
755 def requestZFW(self, callback):
756 """Send a request for the ZFW."""
757 data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
758 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT) ]
759 self._handler.requestRead(data, self._handleZFW, extra = callback)
760
761 def requestWeights(self, callback):
762 """Request the following weights: DOW, ZFW, payload.
763
764 These values will be passed to the callback function in this order, as
765 separate arguments."""
766 data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
767 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
768 ("sim/flightmodel/weight/m_total", TYPE_FLOAT) ]
769 self._handler.requestRead(data, self._handleWeights,
770 extra = callback)
771
772 def requestTime(self, callback):
773 """Request the time from the simulator."""
774 self._handler.requestRead(Simulator.timeData, self._handleTime,
775 extra = callback)
776
777 def startMonitoring(self):
778 """Start the periodic monitoring of the aircraft and pass the resulting
779 state to the aircraft object periodically."""
780 assert not self._monitoringRequested
781 self._monitoringRequested = True
782
783 def stopMonitoring(self):
784 """Stop the periodic monitoring of the aircraft."""
785 assert self._monitoringRequested
786 self._monitoringRequested = False
787
788 def startFlare(self):
789 """Start monitoring the flare time.
790
791 At present it is assumed to be called from the handler thread, hence no
792 protection."""
793 #self._aircraft.logger.debug("startFlare")
794 if self._flareRequestID is None:
795 self._flareRates = []
796 self._flareRequestID = \
797 self._handler.requestPeriodicRead(0.1,
798 Simulator.flareData1,
799 self._handleFlare1)
800
801 def cancelFlare(self):
802 """Cancel monitoring the flare time.
803
804 At present it is assumed to be called from the handler thread, hence no
805 protection."""
806 if self._flareRequestID is not None:
807 self._handler.clearPeriodic(self._flareRequestID)
808 self._flareRequestID = None
809
810 def sendMessage(self, message, duration = 3,
811 _disconnect = False):
812 """Send a message to the pilot via the simulator.
813
814 duration is the number of seconds to keep the message displayed."""
815 print("xplra.Simulator.sendMessage:", message)
816 self._handler.requestShowMessage(message, duration,
817 self._handleMessageSent,
818 extra = _disconnect)
819
820 def getFuel(self, callback):
821 """Get the fuel information for the current model.
822
823 The callback will be called with a list of triplets with the following
824 items:
825 - the fuel tank identifier
826 - the current weight of the fuel in the tank (in kgs)
827 - the current total capacity of the tank (in kgs)."""
828 if self._aircraftModel is None:
829 self._fuelCallback = callback
830 else:
831 self._aircraftModel.getFuel(self._handler, callback)
832
833 def setFuelLevel(self, levels):
834 """Set the fuel level to the given ones.
835
836 levels is an array of two-tuples, where each tuple consists of the
837 following:
838 - the const.FUELTANK_XXX constant denoting the tank that must be set,
839 - the requested level of the fuel as a floating-point value between 0.0
840 and 1.0."""
841 if self._aircraftModel is not None:
842 self._aircraftModel.setFuelLevel(self._handler, levels)
843
844 def enableTimeSync(self):
845 """Enable the time synchronization."""
846 self._nextSyncTime = -1
847 self._syncTime = True
848
849 def disableTimeSync(self):
850 """Enable the time synchronization."""
851 self._syncTime = False
852 self._nextSyncTime = -1
853
854 def listenHotkeys(self, hotkeys, callback):
855 """Start listening to the given hotkeys.
856
857 callback is function expecting two arguments:
858 - the ID of the hotkey set as returned by this function,
859 - the list of the indexes of the hotkeys that were pressed."""
860 with self._hotkeyLock:
861 assert self._hotkeyCodes is None
862
863 self._hotkeyCodes = \
864 [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
865 self._hotkeySetID += 1
866 self._hotkeySetGeneration = 0
867 self._hotkeyCallback = callback
868
869 self._handler.registerHotkeys(self._hotkeyCodes,
870 self._handleHotkeysRegistered,
871 (self._hotkeySetID,
872 self._hotkeySetGeneration))
873
874 return self._hotkeySetID
875
876 def clearHotkeys(self):
877 """Clear the current hotkey set.
878
879 Note that it is possible, that the callback function set either
880 previously or after calling this function by listenHotkeys() will be
881 called with data from the previous hotkey set.
882
883 Therefore it is recommended to store the hotkey set ID somewhere and
884 check that in the callback function. Right before calling
885 clearHotkeys(), this stored ID should be cleared so that the check
886 fails for sure."""
887 with self._hotkeyLock:
888 if self._hotkeyCodes is not None:
889 self._hotkeyCodes = None
890 self._hotkeySetID += 1
891 self._hotkeyCallback = None
892 self._clearHotkeyRequest()
893
894 def disconnect(self, closingMessage = None, duration = 3):
895 """Disconnect from the simulator."""
896 assert not self._monitoringRequested
897
898 print("xplra.Simulator.disconnect", closingMessage, duration)
899
900 self._stopNormal()
901 self.clearHotkeys()
902 if closingMessage is None:
903 self._handler.disconnect()
904 else:
905 self.sendMessage(closingMessage, duration = duration,
906 _disconnect = True)
907
908 def connected(self, fsType, descriptor):
909 """Called when a connection has been established to the flight
910 simulator of the given type."""
911 self._fsType = fsType
912
913 with self._hotkeyLock:
914 if self._hotkeyCodes is not None:
915 self._hotkeySetGeneration += 1
916
917 self._handler.registerHotkeys(self._hotkeyCodes,
918 self._handleHotkeysRegistered,
919 (self._hotkeySetID,
920 self._hotkeySetGeneration))
921
922 self._connectionListener.connected(fsType, descriptor)
923
924 def connectionFailed(self):
925 """Called when the connection could not be established."""
926 with self._hotkeyLock:
927 self._clearHotkeyRequest()
928 self._connectionListener.connectionFailed()
929
930 def disconnected(self):
931 """Called when a connection to the flight simulator has been broken."""
932 with self._hotkeyLock:
933 self._clearHotkeyRequest()
934 self._connectionListener.disconnected()
935
936 def _getTimestamp(self, data):
937 """Convert the given data into a timestamp."""
938 if self._timestampBase is None:
939 year = datetime.date.today().year
940 self._timestampBase = \
941 calendar.timegm(time.struct_time([year, 1, 1, 0, 0, 0, -1, 1, 0]))
942 self._timestampBase += data[0] * 24 * 3600
943 self._timestampDaysOffset = 0
944 self._lastZuluSeconds = None
945
946 zuluSeconds = data[1]
947 if self._lastZuluSeconds is not None and \
948 zuluSeconds<self._lastZuluSeconds:
949 print("xplane.Simulator._getTimestamp: Zulu seconds have gone backwards (%f -> %f), increasing day offset" % \
950 (self._lastZuluSeconds, zuluSeconds))
951 self._timestampDaysOffset += 1
952
953 self._lastZuluSeconds = zuluSeconds
954
955 timestamp = self._timestampBase
956 timestamp += self._timestampDaysOffset * 24 * 3600
957 timestamp += zuluSeconds
958
959 return timestamp
960
961 def _startDefaultNormal(self):
962 """Start the default normal periodic request."""
963 assert self._normalRequestID is None
964 self._timestampBase = None
965 self._normalRequestID = \
966 self._handler.requestPeriodicRead(1.0,
967 Simulator.normalData,
968 self._handleNormal)
969
970 def _stopNormal(self):
971 """Stop the normal period request."""
972 assert self._normalRequestID is not None
973 self._handler.clearPeriodic(self._normalRequestID)
974 self._normalRequestID = None
975 self._monitoring = False
976
977 def _handleNormal(self, data, extra):
978 """Handle the reply to the normal request.
979
980 At the beginning the result consists the data for normalData. When
981 monitoring is started, it contains the result also for the
982 aircraft-specific values.
983 """
984 timestamp = self._getTimestamp(data)
985
986 createdNewModel = self._setAircraftName(timestamp,
987 data.getString(2),
988 data.getString(3),
989 data.getString(4),
990 data.getString(5),
991 data.getString(6),
992 data.getString(7))
993 if self._fuelCallback is not None:
994 self._aircraftModel.getFuel(self._handler, self._fuelCallback)
995 self._fuelCallback = None
996
997 if self._monitoringRequested and not self._monitoring:
998 self._stopNormal()
999 self._startMonitoring()
1000 elif self._monitoring and not self._monitoringRequested:
1001 self._stopNormal()
1002 self._startDefaultNormal()
1003 elif self._monitoring and self._aircraftModel is not None and \
1004 not createdNewModel:
1005 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
1006 timestamp, data)
1007
1008 self._aircraft.handleState(aircraftState)
1009
1010 def _setAircraftName(self, timestamp, tailnum, author, description,
1011 notes, icao, liveryPath):
1012 """Set the name of the aicraft and if it is different from the
1013 previous, create a new model for it.
1014
1015 If so, also notifty the aircraft about the change.
1016
1017 Return if a new model was created."""
1018 author = self._latin1decoder(author)[0]
1019 description = self._latin1decoder(description)[0]
1020 notes = self._latin1decoder(notes)[0]
1021 liveryPath = self._latin1decoder(liveryPath)[0]
1022
1023 aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
1024 if aircraftInfo==self._aircraftInfo:
1025 return False
1026
1027 print("xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
1028 (tailnum, author, description, notes, icao, liveryPath))
1029
1030 self._aircraftInfo = aircraftInfo
1031 needNew = self._aircraftModel is None
1032 needNew = needNew or\
1033 not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
1034 if not needNew:
1035 specialModel = AircraftModel.findSpecial(self._aircraft,
1036 aircraftInfo)
1037 needNew = specialModel is not None and \
1038 specialModel is not self._aircraftModel.__class__
1039
1040 if needNew:
1041 self._setAircraftModel(AircraftModel.create(self._aircraft,
1042 aircraftInfo))
1043
1044 self._aircraft.modelChanged(timestamp, description,
1045 self._aircraftModel.name)
1046
1047 return needNew
1048
1049 def _setAircraftModel(self, model):
1050 """Set a new aircraft model.
1051
1052 It will be queried for the data to monitor and the monitoring request
1053 will be replaced by a new one."""
1054 self._aircraftModel = model
1055 model.simulator = self
1056
1057 if self._monitoring:
1058 self._stopNormal()
1059 self._startMonitoring()
1060
1061 def _startMonitoring(self):
1062 """Start monitoring with the current aircraft model."""
1063 data = Simulator.normalData[:]
1064 self._aircraftModel.addMonitoringData(data, self._fsType)
1065
1066 self._normalRequestID = \
1067 self._handler.requestPeriodicRead(1.0, data,
1068 self._handleNormal)
1069 self._monitoring = True
1070
1071 def _addFlareRate(self, data):
1072 """Append a flare rate to the list of last rates."""
1073 if len(self._flareRates)>=3:
1074 del self._flareRates[0]
1075 self._flareRates.append(data)
1076
1077 def _handleFlare1(self, data, normal):
1078 """Handle the first stage of flare monitoring."""
1079 #self._aircraft.logger.debug("handleFlare1: " + str(data))
1080 if data[1]<=50.0*0.3048:
1081 self._flareStart = time.time()
1082 self._flareStartFS = data[0]
1083 self._handler.clearPeriodic(self._flareRequestID)
1084 self._handler.requestRead(Simulator.flareStartData,
1085 self._handleFlareStart)
1086
1087 self._addFlareRate(data[2])
1088
1089 def _handleFlareStart(self, data, extra):
1090 """Handle the data need to notify the aircraft about the starting of
1091 the flare."""
1092 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
1093 if data is not None:
1094 windDirection = data[1]
1095 if windDirection<0.0: windDirection += 360.0
1096 self._aircraft.flareStarted(data[0], windDirection, data[2],
1097 self._flareStart, self._flareStartFS)
1098
1099 self._flareRequestID = \
1100 self._handler.requestPeriodicRead(0.1,
1101 Simulator.flareData2,
1102 self._handleFlare2)
1103
1104 def _handleFlare2(self, data, normal):
1105 """Handle the first stage of flare monitoring."""
1106 #self._aircraft.logger.debug("handleFlare2: " + str(data))
1107 if data[1]!=0:
1108 flareEnd = time.time()
1109 self._handler.clearPeriodic(self._flareRequestID)
1110 self._flareRequestID = None
1111
1112 flareEndFS = data[0]
1113 if flareEndFS<self._flareStartFS:
1114 flareEndFS += 86400.0
1115
1116 tdRate = min(self._flareRates)
1117 tdRateCalculatedByFS = False
1118
1119 heading = data[6]
1120 if heading<0.0: heading += 360.0
1121
1122 self._aircraft.flareFinished(flareEnd, flareEndFS,
1123 tdRate, tdRateCalculatedByFS,
1124 data[3], data[4], data[5], heading)
1125 else:
1126 self._addFlareRate(data[2])
1127
1128 def _handleZFW(self, data, callback):
1129 """Callback for a ZFW retrieval request."""
1130 zfw = data[0] + data[1]
1131 callback(zfw)
1132
1133 def _handleTime(self, data, callback):
1134 """Callback for a time retrieval request."""
1135 callback(self._getTimestamp(data))
1136
1137 def _handleWeights(self, data, callback):
1138 """Callback for the weights retrieval request."""
1139 dow = data[0]
1140 payload = data[1]
1141 zfw = dow + payload
1142 grossWeight = data[2]
1143 callback(dow, payload, zfw, grossWeight)
1144
1145 def _handleMessageSent(self, success, disconnect):
1146 """Callback for a message sending request."""
1147 #print "xplra.Simulator._handleMessageSent", disconnect
1148 if disconnect:
1149 self._handler.disconnect()
1150
1151 def _handleHotkeysRegistered(self, success, xxx_todo_changeme2):
1152 """Handle the result of the hotkeys having been written."""
1153 (id, generation) = xxx_todo_changeme2
1154 with self._hotkeyLock:
1155 if success and id==self._hotkeySetID and \
1156 generation==self._hotkeySetGeneration:
1157 self._hotkeyRequestID = \
1158 self._handler.requestHotkeysState(0.5,
1159 self._handleHotkeys,
1160 (id, generation))
1161
1162 def _handleHotkeys(self, data, xxx_todo_changeme3):
1163 """Handle the hotkeys."""
1164 (id, generation) = xxx_todo_changeme3
1165 with self._hotkeyLock:
1166 if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
1167 return
1168
1169 callback = self._hotkeyCallback
1170 offsets = self._hotkeyOffets
1171
1172 hotkeysPressed = []
1173 for i in range(0, len(data)):
1174 if data[i]:
1175 hotkeysPressed.append(i)
1176
1177 if hotkeysPressed:
1178 callback(id, hotkeysPressed)
1179
1180 def _clearHotkeyRequest(self):
1181 """Clear the hotkey request in the handler if there is any."""
1182 if self._hotkeyRequestID is not None:
1183 self._handler.unregisterHotkeys(self._hotkeysUnregistered)
1184 self._handler.clearPeriodic(self._hotkeyRequestID)
1185 self._hotkeyRequestID = None
1186
1187 def _hotkeysUnregistered(self, result, extra):
1188 """Called when the hotkeys have been unregistered."""
1189 pass
1190
1191#------------------------------------------------------------------------------
1192
1193class AircraftModel(object):
1194 """Base class for the aircraft models.
1195
1196 Aircraft models handle the data arriving from X-Plane and turn it into an
1197 object describing the aircraft's state."""
1198 monitoringData = [ ("paused",
1199 "sim/time/paused", TYPE_INT),
1200 ("latitude",
1201 "sim/flightmodel/position/latitude", TYPE_DOUBLE),
1202 ("longitude",
1203 "sim/flightmodel/position/longitude", TYPE_DOUBLE),
1204 ("replay",
1205 "sim/operation/prefs/replay_mode", TYPE_INT),
1206 ("overspeed",
1207 "sim/flightmodel/failures/over_vne", TYPE_INT),
1208 ("stalled",
1209 "sim/flightmodel/failures/stallwarning", TYPE_INT),
1210 ("onTheGround",
1211 "sim/flightmodel/failures/onground_any", TYPE_INT),
1212 ("emptyWeight",
1213 "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
1214 ("payloadWeight",
1215 "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
1216 ("grossWeight",
1217 "sim/flightmodel/weight/m_total", TYPE_FLOAT),
1218 ("heading",
1219 "sim/flightmodel/position/psi", TYPE_FLOAT),
1220 ("pitch",
1221 "sim/flightmodel/position/theta", TYPE_FLOAT),
1222 ("bank",
1223 "sim/flightmodel/position/phi", TYPE_FLOAT),
1224 ("ias",
1225 "sim/flightmodel/position/indicated_airspeed2",
1226 TYPE_FLOAT),
1227 ("mach",
1228 "sim/flightmodel/misc/machno", TYPE_FLOAT),
1229 ("groundSpeed",
1230 "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
1231 ("vs",
1232 "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
1233 ("radioAltitude",
1234 "sim/flightmodel/position/y_agl", TYPE_FLOAT),
1235 ("altitude",
1236 "sim/flightmodel/position/elevation", TYPE_FLOAT),
1237 ("gLoad",
1238 "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
1239 ("flapsControl",
1240 "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
1241 ("flapsLeft",
1242 "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
1243 ("flapsRight",
1244 "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
1245 ("navLights",
1246 "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
1247 ("beaconLights",
1248 "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
1249 ("strobeLights",
1250 "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
1251 ("landingLights",
1252 "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
1253 ("pitot",
1254 "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
1255 ("parking",
1256 "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
1257 ("gearControl",
1258 "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
1259 ("noseGear",
1260 "sim/flightmodel2/gear/deploy_ratio",
1261 (TYPE_FLOAT_ARRAY, 1)),
1262 ("spoilers",
1263 "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
1264 ("altimeter",
1265 "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
1266 ("qnh",
1267 "sim/physics/earth_pressure_p", TYPE_FLOAT),
1268 ("nav1",
1269 "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
1270 ("nav1_obs",
1271 "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
1272 ("nav2",
1273 "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
1274 ("nav2_obs",
1275 "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
1276 ("adf1",
1277 "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
1278 ("adf2",
1279 "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
1280 ("squawk",
1281 "sim/cockpit/radios/transponder_code", TYPE_INT),
1282 ("windSpeed",
1283 "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
1284 ("windDirection",
1285 "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
1286 ("visibility",
1287 "sim/weather/visibility_reported_m", TYPE_FLOAT),
1288 ("cog",
1289 "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
1290 ("xpdrC",
1291 "sim/cockpit/radios/transponder_mode", TYPE_INT),
1292 ("apMaster",
1293 "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
1294 ("apState",
1295 "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
1296 ("apHeading",
1297 "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
1298 ("apAltitude",
1299 "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
1300 ("elevatorTrim",
1301 "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
1302 ("antiIceOn",
1303 "sim/cockpit/switches/anti_ice_on", TYPE_INT),
1304 ("surfaceHeat",
1305 "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
1306 ("propHeat",
1307 "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
1308 ("autopilotOn",
1309 "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
1310 ("apHeadingMode",
1311 "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
1312
1313
1314 specialModels = []
1315
1316 @staticmethod
1317 def registerSpecial(clazz):
1318 """Register the given class as a special model."""
1319 AircraftModel.specialModels.append(clazz)
1320
1321 @staticmethod
1322 def findSpecial(aircraft, aircraftInfo):
1323 for specialModel in AircraftModel.specialModels:
1324 if specialModel.doesHandle(aircraft, aircraftInfo):
1325 return specialModel
1326 return None
1327
1328 @staticmethod
1329 def create(aircraft, aircraftInfo):
1330 """Create the model for the given aircraft name, and notify the
1331 aircraft about it."""
1332 specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
1333 if specialModel is not None:
1334 return specialModel()
1335 if aircraft.type in _genericModels:
1336 return _genericModels[aircraft.type]()
1337 else:
1338 return GenericModel()
1339
1340 @staticmethod
1341 def _convertFrequency(value):
1342 """Convert the given frequency value into a string."""
1343 return "%.2f" % (value/100.0,)
1344
1345 @staticmethod
1346 def _convertOBS(value):
1347 """Convert the given OBS value into an integer."""
1348 while value<0.0:
1349 value += 360.0
1350 return int(round(value))
1351
1352 def __init__(self, flapsNotches):
1353 """Construct the aircraft model.
1354
1355 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1356 self._flapsNotches = flapsNotches
1357 self._simulator = None
1358
1359 @property
1360 def name(self):
1361 """Get the name for this aircraft model."""
1362 return "X-Plane/Generic"
1363
1364 @property
1365 def simulator(self):
1366 """Get the simulator this aircraft model works for."""
1367 return self._simulator
1368
1369 @simulator.setter
1370 def simulator(self, simulator):
1371 """Get the simulator this aircraft model works for."""
1372 self._simulator = simulator
1373
1374 def doesHandle(self, aircraft, aircraftInfo):
1375 """Determine if the model handles the given aircraft name.
1376
1377 This default implementation returns False."""
1378 return False
1379
1380 def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
1381 """Add the given X-Plane dataref name and type to the given array and a
1382 member attribute with the given name."""
1383 dest.append((name, type))
1384 if attrName is not None:
1385 setattr(self, attrName, len(dest)-1)
1386
1387 def _addDataWithIndexMembers(self, dest, prefix, data):
1388 """Add X-Plane dataref data to the given array and also corresponding
1389 index member variables with the given prefix.
1390
1391 data is a list of triplets of the following items:
1392 - the name of the data item. The index member variable will have a name
1393 created by prepending the given prefix to this name.
1394 - the X-Plane dataref name
1395 - the dataref type
1396
1397 The latter two items will be appended to dest."""
1398 for (name, datarefName, type) in data:
1399 self._addDatarefWithIndexMember(dest, datarefName, type,
1400 prefix + name)
1401
1402 def addMonitoringData(self, data, fsType):
1403 """Add the model-specific monitoring data to the given array."""
1404 self._addDataWithIndexMembers(data, "_monidx_",
1405 AircraftModel.monitoringData)
1406
1407 def getAircraftState(self, aircraft, timestamp, data):
1408 """Get an aircraft state object for the given monitoring data."""
1409 state = fs.AircraftState()
1410
1411 lnavOn = data[self._monidx_autopilotOn]!=0 and \
1412 data[self._monidx_apHeadingMode]==2
1413
1414 state.timestamp = timestamp
1415
1416 state.latitude = data[self._monidx_latitude]
1417 state.longitude = data[self._monidx_longitude]
1418 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1419
1420 state.paused = data[self._monidx_paused]!=0 or \
1421 data[self._monidx_replay]!=0
1422 state.trickMode = data[self._monidx_replay]!=0
1423
1424 state.overspeed = data[self._monidx_overspeed]!=0
1425 state.stalled = data[self._monidx_stalled]!=0
1426 state.onTheGround = data[self._monidx_onTheGround]!=0
1427
1428 state.zfw = data[self._monidx_emptyWeight] + \
1429 data[self._monidx_payloadWeight]
1430 state.grossWeight = data[self._monidx_grossWeight]
1431
1432 state.heading = data[self._monidx_heading]
1433
1434 state.pitch = -1.0 * data[self._monidx_pitch]
1435 state.bank = data[self._monidx_bank]
1436
1437 state.ias = data[self._monidx_ias]
1438 state.mach = data[self._monidx_mach]
1439 state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
1440 state.vs = data[self._monidx_vs]
1441
1442 state.radioAltitude = data[self._monidx_radioAltitude]/.3048
1443 state.altitude = data[self._monidx_altitude]/.3048
1444
1445 state.gLoad = data[self._monidx_gLoad]
1446
1447 flapsControl = data[self._monidx_flapsControl]
1448 flapsIndex = int(round(flapsControl * (len(self._flapsNotches)-1)))
1449 state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
1450
1451 state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
1452
1453 state.navLightsOn = data[self._monidx_navLights] != 0
1454 state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
1455 state.landingLightsOn = data[self._monidx_landingLights] != 0
1456 state.strobeLightsOn = data[self._monidx_strobeLights] != 0
1457
1458 state.pitotHeatOn = data[self._monidx_pitot]!=0
1459
1460 state.parking = data[self._monidx_parking]>=0.5
1461
1462 state.gearControlDown = data[self._monidx_gearControl]!=0
1463 state.gearsDown = data[self._monidx_noseGear][0]>0.99
1464
1465 state.spoilersArmed = None
1466
1467 state.spoilersExtension = data[self._monidx_spoilers]*100.0
1468
1469 state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
1470 state.altimeterReliable = True
1471 state.qnh = data[self._monidx_qnh]/100.0
1472
1473 state.ils = None
1474 state.ils_obs = None
1475 state.ils_manual = False
1476 state.nav1 = self._convertFrequency(data[self._monidx_nav1])
1477 state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
1478 state.nav1_manual = True
1479 state.nav2 = self._convertFrequency(data[self._monidx_nav2])
1480 state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
1481 state.nav2_manual = not lnavOn
1482 state.adf1 = str(data[self._monidx_adf1])
1483 state.adf2 = str(data[self._monidx_adf2])
1484
1485 state.squawk = "%04d" % (data[self._monidx_squawk],)
1486
1487 state.windSpeed = data[self._monidx_windSpeed]
1488 state.windDirection = data[self._monidx_windDirection]
1489 if state.windDirection<0.0: state.windDirection += 360.0
1490
1491 state.visibility = data[self._monidx_visibility]
1492
1493 state.cog = data[self._monidx_cog]
1494
1495 state.xpdrC = data[self._monidx_xpdrC]==2
1496 state.autoXPDR = False
1497
1498 state.apMaster = data[self._monidx_apMaster]==2
1499 apState = data[self._monidx_apState]
1500 if lnavOn:
1501 state.apHeadingHold = None
1502 state.apHeading = None
1503 else:
1504 state.apHeadingHold = (apState&0x00002)!=0
1505 state.apHeading = data[self._monidx_apHeading]
1506
1507 state.apAltitudeHold = (apState&0x04000)!=0
1508 state.apAltitude = data[self._monidx_apAltitude]
1509
1510 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1511
1512 state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
1513 data[self._monidx_surfaceHeat]!=0 or \
1514 data[self._monidx_propHeat]!=0
1515
1516 return state
1517
1518#------------------------------------------------------------------------------
1519
1520class GenericAircraftModel(AircraftModel):
1521 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1522 values and some other common parameters in a generic way."""
1523
1524 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1525 """Construct the generic aircraft model with the given data.
1526
1527 flapsNotches is an array of how much degrees the individual flaps
1528 notches mean.
1529
1530 fuelTanks is an array of const.FUELTANK_XXX constants about the
1531 aircraft's fuel tanks. They will be converted to offsets.
1532
1533 numEngines is the number of engines the aircraft has.
1534
1535 isN1 determines if the engines have an N1 value or an RPM value
1536 (e.g. pistons)."""
1537 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1538
1539 self._fuelTanks = fuelTanks
1540 self._fuelTankCapacities = [1.0] * len(fuelTanks)
1541 self._fuelIndex = None
1542 self._numEngines = numEngines
1543 self._engineStartIndex = None
1544 self._isN1 = isN1
1545
1546 def doesHandle(self, aircraft, aircraftInfo):
1547 """Determine if the model handles the given aircraft name.
1548
1549 This implementation returns True."""
1550 return True
1551
1552 def addMonitoringData(self, data, fsType):
1553 """Add the model-specific monitoring data to the given array."""
1554 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
1555
1556 self._fuelIndex = self._addFuelData(data)
1557
1558 self._engineStartIndex = len(data)
1559 if self._isN1:
1560 self._addDatarefWithIndexMember(data,
1561 "sim/flightmodel/engine/ENGN_N1_",
1562 (TYPE_FLOAT_ARRAY,
1563 self._numEngines))
1564 else:
1565 self._addDatarefWithIndexMember(data,
1566 "sim/flightmodel/engine/POINT_tacrad",
1567 (TYPE_FLOAT_ARRAY,
1568 self._numEngines))
1569
1570 self._addDatarefWithIndexMember(data,
1571 "sim/flightmodel/engine/ENGN_propmode",
1572 (TYPE_INT_ARRAY, self._numEngines))
1573
1574 def getAircraftState(self, aircraft, timestamp, data):
1575 """Get the aircraft state.
1576
1577 Get it from the parent, and then add the data about the fuel levels and
1578 the engine parameters."""
1579 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1580 timestamp,
1581 data)
1582
1583 state.fuel = []
1584 state.totalFuel = 0.0
1585
1586 fuelAmounts = data[self._fuelIndex]
1587 for i in range(0, len(self._fuelTanks)):
1588 amount = fuelAmounts[i]
1589 state.fuel.append((self._fuelTanks[i], amount))
1590 state.totalFuel += amount
1591
1592 power = data[self._engineStartIndex]
1593
1594 state.n1 = power[:] if self._isN1 else None
1595 state.rpm = None if self._isN1 else power[:]
1596
1597 propMode = data[self._engineStartIndex+1]
1598 state.reverser = [mode == 3 for mode in propMode]
1599
1600 return state
1601
1602 def getFuel(self, handler, callback):
1603 """Get the fuel information for this model.
1604
1605 See Simulator.getFuel for more information. This
1606 implementation simply queries the fuel tanks given to the
1607 constructor."""
1608 data = []
1609 self._addFuelData(data)
1610 data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
1611 data.append( ("sim/aircraft/overflow/acf_tank_rat",
1612 (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
1613
1614 handler.requestRead(data, self._handleFuelRetrieved,
1615 extra = callback)
1616
1617 def setFuelLevel(self, handler, levels):
1618 """Set the fuel level.
1619
1620 See the description of Simulator.setFuelLevel. This
1621 implementation simply sets the fuel tanks as given."""
1622 data = []
1623 for (tank, level) in levels:
1624 try:
1625 index = self._fuelTanks.index(tank)
1626 data.append( ("sim/flightmodel/weight/m_fuel",
1627 (TYPE_FLOAT_ARRAY, 1, index),
1628 [level * self._fuelTankCapacities[index]]) )
1629 except:
1630 print("xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
1631 (tank,))
1632
1633 handler.requestWrite(data, self._handleFuelWritten)
1634
1635 def _addFuelData(self, data):
1636 """Add the fuel offsets to the given data array.
1637
1638 Returns the index of the first fuel tank's data."""
1639 fuelStartIndex = len(data)
1640 data.append( ("sim/flightmodel/weight/m_fuel",
1641 (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
1642
1643 return fuelStartIndex
1644
1645 def _convertFuelData(self, data, index = 0, addCapacities = False):
1646 """Convert the given data into a fuel info list.
1647
1648 The list consists of two or three-tuples of the following
1649 items:
1650 - the fuel tank ID,
1651 - the amount of the fuel in kg,
1652 - if addCapacities is True, the total capacity of the tank."""
1653 fuelWeight = data[index] / 256.0
1654 index += 1
1655
1656 result = []
1657 totalFuel = 0
1658 for fuelTank in self._fuelTanks:
1659 capacity = data[index+1] * fuelWeight * const.LBSTOKG
1660 if capacity>=1.0:
1661 amount = data[index] * capacity / 128.0 / 65536.0
1662
1663 result.append( (fuelTank, amount, capacity) if addCapacities
1664 else (fuelTank, amount))
1665 totalFuel += amount
1666 index += 2
1667
1668 return (result, totalFuel)
1669
1670 def _handleFuelRetrieved(self, data, callback):
1671 """Callback for a fuel retrieval request."""
1672 result = []
1673 totalCapacity = data[1]
1674 for index in range(0, len(self._fuelTanks)):
1675 amount = data[0][index]
1676 capacity = data[2][index] * totalCapacity
1677 self._fuelTankCapacities[index] = capacity
1678 result.append( (self._fuelTanks[index], amount, capacity) )
1679
1680 callback(result)
1681
1682 def _handleFuelWritten(self, success, extra):
1683 """Callback for a fuel setting request."""
1684 pass
1685
1686#------------------------------------------------------------------------------
1687
1688class GenericModel(GenericAircraftModel):
1689 """Generic aircraft model for an unknown type."""
1690 def __init__(self):
1691 """Construct the model."""
1692 super(GenericModel, self). \
1693 __init__(flapsNotches = [0, 10, 20, 30],
1694 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1695 numEngines = 2)
1696
1697 @property
1698 def name(self):
1699 """Get the name for this aircraft model."""
1700 return "X-Plane/Generic"
1701
1702#------------------------------------------------------------------------------
1703
1704class B737Model(GenericAircraftModel):
1705 """Generic model for the Boeing 737 Classing and NG aircraft."""
1706 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1707
1708 def __init__(self):
1709 """Construct the model."""
1710 super(B737Model, self). \
1711 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1712 fuelTanks = B737Model.fuelTanks,
1713 numEngines = 2)
1714
1715 @property
1716 def name(self):
1717 """Get the name for this aircraft model."""
1718 return "X-Plane/Generic Boeing 737"
1719
1720#------------------------------------------------------------------------------
1721
1722class B767Model(GenericAircraftModel):
1723 """Generic model for the Boeing 767 aircraft."""
1724 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1725
1726 def __init__(self):
1727 """Construct the model."""
1728 super(B767Model, self). \
1729 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1730 fuelTanks = B767Model.fuelTanks,
1731 numEngines = 2)
1732
1733 @property
1734 def name(self):
1735 """Get the name for this aircraft model."""
1736 return "X-Plane/Generic Boeing 767"
1737
1738#------------------------------------------------------------------------------
1739
1740class DH8DModel(GenericAircraftModel):
1741 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1742 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1743
1744 def __init__(self):
1745 """Construct the model."""
1746 super(DH8DModel, self). \
1747 __init__(flapsNotches = [0, 5, 10, 15, 35],
1748 fuelTanks = DH8DModel.fuelTanks,
1749 numEngines = 2)
1750
1751 @property
1752 def name(self):
1753 """Get the name for this aircraft model."""
1754 return "X-Plane/Generic Bombardier Dash 8-Q400"
1755
1756#------------------------------------------------------------------------------
1757
1758class FJSDH8DModel(DH8DModel):
1759 """Model handler for the FlyJSim Dash 8-Q400."""
1760 @staticmethod
1761 def doesHandle(aircraft, xxx_todo_changeme):
1762 """Determine if this model handler handles the aircraft with the given
1763 name."""
1764 (tailnum, author, description, notes,
1765 icao, liveryPath) = xxx_todo_changeme
1766 return aircraft.type==const.AIRCRAFT_DH8D and \
1767 description.find("Dash 8 Q400")!=-1 and \
1768 ((author in ["2012", "2013"] and tailnum=="N62890") or \
1769 author.find("Jack Skieczius")!=-1)
1770
1771 @property
1772 def name(self):
1773 """Get the name for this aircraft model."""
1774 return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
1775
1776 def addMonitoringData(self, data, fsType):
1777 """Add the model-specific monitoring data to the given array."""
1778 super(FJSDH8DModel, self).addMonitoringData(data, fsType)
1779
1780 self._speedBrakeIndex = len(data)
1781 self._addDatarefWithIndexMember(data,
1782 "sim/flightmodel2/wing/speedbrake1_deg",
1783 (TYPE_FLOAT_ARRAY, 2))
1784 self._addDatarefWithIndexMember(data,
1785 "sim/flightmodel2/wing/speedbrake2_deg",
1786 (TYPE_FLOAT_ARRAY, 2))
1787
1788
1789 def getAircraftState(self, aircraft, timestamp, data):
1790 """Get the aircraft state.
1791
1792 Get it from the parent, and then invert the pitot heat state."""
1793 state = super(FJSDH8DModel, self).getAircraftState(aircraft,
1794 timestamp,
1795 data)
1796 state.antiCollisionLightsOn = \
1797 state.antiCollisionLightsOn or state.strobeLightsOn
1798 state.cog = (state.cog / 0.0254 + 21.504) / 94.512
1799
1800 # It seems that N1 does not always go down to 0 properly
1801 # (maybe due to winds?)
1802 state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
1803
1804 state.spoilersExtension = \
1805 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1806
1807 return state
1808
1809#------------------------------------------------------------------------------
1810
1811class CRJ2Model(GenericAircraftModel):
1812 """Generic model for the Bombardier CRJ-200 aircraft."""
1813 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1814
1815 def __init__(self):
1816 """Construct the model."""
1817 super(CRJ2Model, self). \
1818 __init__(flapsNotches = [0, 8, 20, 30, 45],
1819 fuelTanks = CRJ2Model.fuelTanks,
1820 numEngines = 2)
1821
1822 @property
1823 def name(self):
1824 """Get the name for this aircraft model."""
1825 return "X-Plane/Generic Bombardier CRJ-200"
1826
1827#------------------------------------------------------------------------------
1828
1829class F70Model(GenericAircraftModel):
1830 """Generic model for the Fokker F70 aircraft."""
1831 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1832
1833 def __init__(self):
1834 """Construct the model."""
1835 super(F70Model, self). \
1836 __init__(flapsNotches = [0, 8, 15, 25, 42],
1837 fuelTanks = F70Model.fuelTanks,
1838 numEngines = 2)
1839
1840 @property
1841 def name(self):
1842 """Get the name for this aircraft model."""
1843 return "X-Plane/Generic Fokker 70"
1844
1845#------------------------------------------------------------------------------
1846
1847class DC3Model(GenericAircraftModel):
1848 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
1849 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
1850 const.FUELTANK_RIGHT]
1851 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1852 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1853
1854 def __init__(self):
1855 """Construct the model."""
1856 super(DC3Model, self). \
1857 __init__(flapsNotches = [0, 15, 30, 45],
1858 fuelTanks = DC3Model.fuelTanks,
1859 numEngines = 2, isN1 = False)
1860 self._leftLevel = 0.0
1861 self._rightLevel = 0.0
1862
1863 @property
1864 def name(self):
1865 """Get the name for this aircraft model."""
1866 return "X-Plane/Generic Lisunov Li-2 (DC-3)"
1867
1868#------------------------------------------------------------------------------
1869
1870class T134Model(GenericAircraftModel):
1871 """Generic model for the Tupolev Tu-134 aircraft."""
1872 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1873 const.FUELTANK_LEFT_AUX,
1874 const.FUELTANK_CENTRE,
1875 const.FUELTANK_RIGHT_AUX,
1876 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1877
1878 def __init__(self):
1879 """Construct the model."""
1880 super(T134Model, self). \
1881 __init__(flapsNotches = [0, 10, 20, 30],
1882 fuelTanks = T134Model.fuelTanks,
1883 numEngines = 2)
1884
1885 @property
1886 def name(self):
1887 """Get the name for this aircraft model."""
1888 return "X-Plane/Generic Tupolev Tu-134"
1889
1890#------------------------------------------------------------------------------
1891
1892class T154Model(GenericAircraftModel):
1893 """Generic model for the Tupolev Tu-154 aircraft."""
1894 fuelTanks = [const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
1895 const.FUELTANK_RIGHT, const.FUELTANK_LEFT,
1896 const.FUELTANK_RIGHT_AUX, const.FUELTANK_LEFT_AUX]
1897
1898 def __init__(self):
1899 """Construct the model."""
1900 super(T154Model, self). \
1901 __init__(flapsNotches = [0, 15, 28, 45],
1902 fuelTanks = T154Model.fuelTanks,
1903 numEngines = 3)
1904
1905 @property
1906 def name(self):
1907 """Get the name for this aircraft model."""
1908 return "X-Plane/Generic Tupolev Tu-154"
1909
1910 def getAircraftState(self, aircraft, timestamp, data):
1911 """Get an aircraft state object for the given monitoring data.
1912
1913 This removes the reverser value for the middle engine."""
1914 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
1915 del state.reverser[1]
1916 return state
1917
1918#------------------------------------------------------------------------------
1919
1920class FelisT154Model(T154Model):
1921 """Model for Felis' Tupolev Tu-154-M aircraft."""
1922 @staticmethod
1923 def doesHandle(aircraft, xxx_todo_changeme1):
1924 """Determine if this model handler handles the aircraft with the given
1925 name."""
1926 (tailnum, author, description, notes,
1927 icao, liveryPath) = xxx_todo_changeme1
1928 return aircraft.type==const.AIRCRAFT_T154 and \
1929 author.find("Felis")!=-1 and \
1930 description.find("Tu154M")!=-1
1931
1932 def __init__(self):
1933 """Construct the model."""
1934 super(T154Model, self). \
1935 __init__(flapsNotches = [0, 15, 28, 36, 45],
1936 fuelTanks = T154Model.fuelTanks,
1937 numEngines = 3)
1938
1939 @property
1940 def name(self):
1941 """Get the name for this aircraft model."""
1942 return "X-Plane/Felis Tupolev Tu-154-M"
1943
1944#------------------------------------------------------------------------------
1945
1946class YK40Model(GenericAircraftModel):
1947 """Generic model for the Yakovlev Yak-40 aircraft."""
1948 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1949
1950 def __init__(self):
1951 """Construct the model."""
1952 super(YK40Model, self). \
1953 __init__(flapsNotches = [0, 20, 35],
1954 fuelTanks = YK40Model.fuelTanks,
1955 numEngines = 2)
1956
1957 @property
1958 def name(self):
1959 """Get the name for this aircraft model."""
1960 return "X-Plane/Generic Yakovlev Yak-40"
1961
1962#------------------------------------------------------------------------------
1963
1964_genericModels = { const.AIRCRAFT_B736 : B737Model,
1965 const.AIRCRAFT_B737 : B737Model,
1966 const.AIRCRAFT_B738 : B737Model,
1967 const.AIRCRAFT_B738C : B737Model,
1968 const.AIRCRAFT_B732 : B737Model,
1969 const.AIRCRAFT_B733 : B737Model,
1970 const.AIRCRAFT_B734 : B737Model,
1971 const.AIRCRAFT_B735 : B737Model,
1972 const.AIRCRAFT_DH8D : DH8DModel,
1973 const.AIRCRAFT_B762 : B767Model,
1974 const.AIRCRAFT_B763 : B767Model,
1975 const.AIRCRAFT_CRJ2 : CRJ2Model,
1976 const.AIRCRAFT_F70 : F70Model,
1977 const.AIRCRAFT_DC3 : DC3Model,
1978 const.AIRCRAFT_T134 : T134Model,
1979 const.AIRCRAFT_T154 : T154Model,
1980 const.AIRCRAFT_YK40 : YK40Model }
1981
1982#------------------------------------------------------------------------------
1983
1984AircraftModel.registerSpecial(FJSDH8DModel)
1985AircraftModel.registerSpecial(FelisT154Model)
1986
1987#------------------------------------------------------------------------------
1988
1989# if __name__ == "__main__":
1990# class ConnectionListener:
1991# def connected(self, fsType, descriptor):
1992# """Called when a connection has been established to the flight
1993# simulator of the given type."""
1994# print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
1995
1996# def connectionFailed(self):
1997# """Called when the connection could not be established."""
1998# print "fs.ConnectionListener.connectionFailed"
1999
2000# def disconnected(self):
2001# """Called when a connection to the flight simulator has been broken."""
2002# print "fs.ConnectionListener.disconnected"
2003
2004# class Config:
2005# def __init__(self):
2006# self.onlineACARS = False
2007# self.realIASSmoothingLength = 2
2008# self.realVSSmoothingLength = 2
2009# self.enableSounds = False
2010# self.usingFS2Crew = False
2011
2012# def isMessageTypeFS(self, type):
2013# return True
2014
2015
2016
2017# class GUI:
2018# def __init__(self):
2019# self.config = Config()
2020# self.entranceExam = False
2021# self.zfw = 30000.0
2022
2023# def resetFlightStatus(self):
2024# pass
2025
2026# def setRating(self, value):
2027# pass
2028
2029# def insertFlightLogLine(self, index, ts, text, isFault):
2030# pass
2031
2032# def setStage(self, stage):
2033# pass
2034
2035
2036# from i18n import setLanguage
2037
2038# setLanguage("/home/vi/munka/repules/mlx", "en")
2039
2040# from logger import Logger
2041# from flight import Flight
2042# from acft import DH8D
2043
2044# gui = GUI()
2045
2046# logger = Logger(gui)
2047
2048# flight = Flight(logger, gui)
2049# acft = DH8D(flight)
2050
2051# Watchdog()
2052
2053# connectionListener = ConnectionListener()
2054# simulator = Simulator(connectionListener, connectAttempts = 3)
2055
2056# simulator.connect(acft)
2057
2058# time.sleep(2)
2059
2060# simulator.startMonitoring()
2061
2062# simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
2063
2064# time.sleep(4)
2065
2066# 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)
2067# #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)
2068# #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)
2069
2070# time.sleep(30)
2071
2072# simulator.sendMessage("[MLX] Hello", duration = 3)
2073
2074# time.sleep(10)
Note: See TracBrowser for help on using the repository browser.