source: src/mlx/xplane.py@ 919:2ce8ca39525b

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

Ran 2to3

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