source: src/mlx/xplane.py@ 909:5df186bc203c

version_0.39_maint
Last change on this file since 909:5df186bc203c was 909:5df186bc203c, checked in by István Váradi <ivaradi@…>, 4 years ago

Explicit support for X-Plane 11.

File size: 77.9 KB
Line 
1
2import fs
3import const
4import 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, 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, e:
244 print >> sys.stderr, util.utf2unicode(str(e))
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, 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, 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, (id, generation)):
1143 """Handle the result of the hotkeys having been written."""
1144 with self._hotkeyLock:
1145 if success and id==self._hotkeySetID and \
1146 generation==self._hotkeySetGeneration:
1147 self._hotkeyRequestID = \
1148 self._handler.requestHotkeysState(0.5,
1149 self._handleHotkeys,
1150 (id, generation))
1151
1152 def _handleHotkeys(self, data, (id, generation)):
1153 """Handle the hotkeys."""
1154 with self._hotkeyLock:
1155 if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
1156 return
1157
1158 callback = self._hotkeyCallback
1159 offsets = self._hotkeyOffets
1160
1161 hotkeysPressed = []
1162 for i in range(0, len(data)):
1163 if data[i]:
1164 hotkeysPressed.append(i)
1165
1166 if hotkeysPressed:
1167 callback(id, hotkeysPressed)
1168
1169 def _clearHotkeyRequest(self):
1170 """Clear the hotkey request in the handler if there is any."""
1171 if self._hotkeyRequestID is not None:
1172 self._handler.unregisterHotkeys(self._hotkeysUnregistered)
1173 self._handler.clearPeriodic(self._hotkeyRequestID)
1174 self._hotkeyRequestID = None
1175
1176 def _hotkeysUnregistered(self, result, extra):
1177 """Called when the hotkeys have been unregistered."""
1178 pass
1179
1180#------------------------------------------------------------------------------
1181
1182class AircraftModel(object):
1183 """Base class for the aircraft models.
1184
1185 Aircraft models handle the data arriving from X-Plane and turn it into an
1186 object describing the aircraft's state."""
1187 monitoringData = [ ("paused",
1188 "sim/time/paused", TYPE_INT),
1189 ("latitude",
1190 "sim/flightmodel/position/latitude", TYPE_DOUBLE),
1191 ("longitude",
1192 "sim/flightmodel/position/longitude", TYPE_DOUBLE),
1193 ("replay",
1194 "sim/operation/prefs/replay_mode", TYPE_INT),
1195 ("overspeed",
1196 "sim/flightmodel/failures/over_vne", TYPE_INT),
1197 ("stalled",
1198 "sim/flightmodel/failures/stallwarning", TYPE_INT),
1199 ("onTheGround",
1200 "sim/flightmodel/failures/onground_any", TYPE_INT),
1201 ("emptyWeight",
1202 "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
1203 ("payloadWeight",
1204 "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
1205 ("grossWeight",
1206 "sim/flightmodel/weight/m_total", TYPE_FLOAT),
1207 ("heading",
1208 "sim/flightmodel/position/psi", TYPE_FLOAT),
1209 ("pitch",
1210 "sim/flightmodel/position/theta", TYPE_FLOAT),
1211 ("bank",
1212 "sim/flightmodel/position/phi", TYPE_FLOAT),
1213 ("ias",
1214 "sim/flightmodel/position/indicated_airspeed2",
1215 TYPE_FLOAT),
1216 ("mach",
1217 "sim/flightmodel/misc/machno", TYPE_FLOAT),
1218 ("groundSpeed",
1219 "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
1220 ("vs",
1221 "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
1222 ("radioAltitude",
1223 "sim/flightmodel/position/y_agl", TYPE_FLOAT),
1224 ("altitude",
1225 "sim/flightmodel/position/elevation", TYPE_FLOAT),
1226 ("gLoad",
1227 "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
1228 ("flapsControl",
1229 "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
1230 ("flapsLeft",
1231 "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
1232 ("flapsRight",
1233 "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
1234 ("navLights",
1235 "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
1236 ("beaconLights",
1237 "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
1238 ("strobeLights",
1239 "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
1240 ("landingLights",
1241 "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
1242 ("pitot",
1243 "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
1244 ("parking",
1245 "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
1246 ("gearControl",
1247 "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
1248 ("noseGear",
1249 "sim/flightmodel2/gear/deploy_ratio",
1250 (TYPE_FLOAT_ARRAY, 1)),
1251 ("spoilers",
1252 "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
1253 ("altimeter",
1254 "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
1255 ("qnh",
1256 "sim/physics/earth_pressure_p", TYPE_FLOAT),
1257 ("nav1",
1258 "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
1259 ("nav1_obs",
1260 "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
1261 ("nav2",
1262 "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
1263 ("nav2_obs",
1264 "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
1265 ("adf1",
1266 "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
1267 ("adf2",
1268 "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
1269 ("squawk",
1270 "sim/cockpit/radios/transponder_code", TYPE_INT),
1271 ("windSpeed",
1272 "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
1273 ("windDirection",
1274 "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
1275 ("visibility",
1276 "sim/weather/visibility_reported_m", TYPE_FLOAT),
1277 ("cog",
1278 "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
1279 ("xpdrC",
1280 "sim/cockpit/radios/transponder_mode", TYPE_INT),
1281 ("apMaster",
1282 "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
1283 ("apState",
1284 "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
1285 ("apHeading",
1286 "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
1287 ("apAltitude",
1288 "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
1289 ("elevatorTrim",
1290 "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
1291 ("antiIceOn",
1292 "sim/cockpit/switches/anti_ice_on", TYPE_INT),
1293 ("surfaceHeat",
1294 "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
1295 ("propHeat",
1296 "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
1297 ("autopilotOn",
1298 "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
1299 ("apHeadingMode",
1300 "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
1301
1302
1303 specialModels = []
1304
1305 @staticmethod
1306 def registerSpecial(clazz):
1307 """Register the given class as a special model."""
1308 AircraftModel.specialModels.append(clazz)
1309
1310 @staticmethod
1311 def findSpecial(aircraft, aircraftInfo):
1312 for specialModel in AircraftModel.specialModels:
1313 if specialModel.doesHandle(aircraft, aircraftInfo):
1314 return specialModel
1315 return None
1316
1317 @staticmethod
1318 def create(aircraft, aircraftInfo):
1319 """Create the model for the given aircraft name, and notify the
1320 aircraft about it."""
1321 specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
1322 if specialModel is not None:
1323 return specialModel()
1324 if aircraft.type in _genericModels:
1325 return _genericModels[aircraft.type]()
1326 else:
1327 return GenericModel()
1328
1329 @staticmethod
1330 def _convertFrequency(value):
1331 """Convert the given frequency value into a string."""
1332 return "%.2f" % (value/100.0,)
1333
1334 @staticmethod
1335 def _convertOBS(value):
1336 """Convert the given OBS value into an integer."""
1337 while value<0.0:
1338 value += 360.0
1339 return int(round(value))
1340
1341 def __init__(self, flapsNotches):
1342 """Construct the aircraft model.
1343
1344 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1345 self._flapsNotches = flapsNotches
1346 self._simulator = None
1347
1348 @property
1349 def name(self):
1350 """Get the name for this aircraft model."""
1351 return "X-Plane/Generic"
1352
1353 @property
1354 def simulator(self):
1355 """Get the simulator this aircraft model works for."""
1356 return self._simulator
1357
1358 @simulator.setter
1359 def simulator(self, simulator):
1360 """Get the simulator this aircraft model works for."""
1361 self._simulator = simulator
1362
1363 def doesHandle(self, aircraft, aircraftInfo):
1364 """Determine if the model handles the given aircraft name.
1365
1366 This default implementation returns False."""
1367 return False
1368
1369 def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
1370 """Add the given X-Plane dataref name and type to the given array and a
1371 member attribute with the given name."""
1372 dest.append((name, type))
1373 if attrName is not None:
1374 setattr(self, attrName, len(dest)-1)
1375
1376 def _addDataWithIndexMembers(self, dest, prefix, data):
1377 """Add X-Plane dataref data to the given array and also corresponding
1378 index member variables with the given prefix.
1379
1380 data is a list of triplets of the following items:
1381 - the name of the data item. The index member variable will have a name
1382 created by prepending the given prefix to this name.
1383 - the X-Plane dataref name
1384 - the dataref type
1385
1386 The latter two items will be appended to dest."""
1387 for (name, datarefName, type) in data:
1388 self._addDatarefWithIndexMember(dest, datarefName, type,
1389 prefix + name)
1390
1391 def addMonitoringData(self, data, fsType):
1392 """Add the model-specific monitoring data to the given array."""
1393 self._addDataWithIndexMembers(data, "_monidx_",
1394 AircraftModel.monitoringData)
1395
1396 def getAircraftState(self, aircraft, timestamp, data):
1397 """Get an aircraft state object for the given monitoring data."""
1398 state = fs.AircraftState()
1399
1400 lnavOn = data[self._monidx_autopilotOn]!=0 and \
1401 data[self._monidx_apHeadingMode]==2
1402
1403 state.timestamp = timestamp
1404
1405 state.latitude = data[self._monidx_latitude]
1406 state.longitude = data[self._monidx_longitude]
1407 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1408
1409 state.paused = data[self._monidx_paused]!=0 or \
1410 data[self._monidx_replay]!=0
1411 state.trickMode = data[self._monidx_replay]!=0
1412
1413 state.overspeed = data[self._monidx_overspeed]!=0
1414 state.stalled = data[self._monidx_stalled]!=0
1415 state.onTheGround = data[self._monidx_onTheGround]!=0
1416
1417 state.zfw = data[self._monidx_emptyWeight] + \
1418 data[self._monidx_payloadWeight]
1419 state.grossWeight = data[self._monidx_grossWeight]
1420
1421 state.heading = data[self._monidx_heading]
1422
1423 state.pitch = -1.0 * data[self._monidx_pitch]
1424 state.bank = data[self._monidx_bank]
1425
1426 state.ias = data[self._monidx_ias]
1427 state.mach = data[self._monidx_mach]
1428 state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
1429 state.vs = data[self._monidx_vs]
1430
1431 state.radioAltitude = data[self._monidx_radioAltitude]/.3048
1432 state.altitude = data[self._monidx_altitude]/.3048
1433
1434 state.gLoad = data[self._monidx_gLoad]
1435
1436 flapsControl = data[self._monidx_flapsControl]
1437 flapsIndex = int(round(flapsControl * (len(self._flapsNotches)-1)))
1438 state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
1439
1440 state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
1441
1442 state.navLightsOn = data[self._monidx_navLights] != 0
1443 state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
1444 state.landingLightsOn = data[self._monidx_landingLights] != 0
1445 state.strobeLightsOn = data[self._monidx_strobeLights] != 0
1446
1447 state.pitotHeatOn = data[self._monidx_pitot]!=0
1448
1449 state.parking = data[self._monidx_parking]>=0.5
1450
1451 state.gearControlDown = data[self._monidx_gearControl]!=0
1452 state.gearsDown = data[self._monidx_noseGear][0]>0.99
1453
1454 state.spoilersArmed = None
1455
1456 state.spoilersExtension = data[self._monidx_spoilers]*100.0
1457
1458 state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
1459 state.altimeterReliable = True
1460 state.qnh = data[self._monidx_qnh]/100.0
1461
1462 state.ils = None
1463 state.ils_obs = None
1464 state.ils_manual = False
1465 state.nav1 = self._convertFrequency(data[self._monidx_nav1])
1466 state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
1467 state.nav1_manual = True
1468 state.nav2 = self._convertFrequency(data[self._monidx_nav2])
1469 state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
1470 state.nav2_manual = not lnavOn
1471 state.adf1 = str(data[self._monidx_adf1])
1472 state.adf2 = str(data[self._monidx_adf2])
1473
1474 state.squawk = "%04d" % (data[self._monidx_squawk],)
1475
1476 state.windSpeed = data[self._monidx_windSpeed]
1477 state.windDirection = data[self._monidx_windDirection]
1478 if state.windDirection<0.0: state.windDirection += 360.0
1479
1480 state.visibility = data[self._monidx_visibility]
1481
1482 state.cog = data[self._monidx_cog]
1483
1484 state.xpdrC = data[self._monidx_xpdrC]==2
1485 state.autoXPDR = False
1486
1487 state.apMaster = data[self._monidx_apMaster]==2
1488 apState = data[self._monidx_apState]
1489 if lnavOn:
1490 state.apHeadingHold = None
1491 state.apHeading = None
1492 else:
1493 state.apHeadingHold = (apState&0x00002)!=0
1494 state.apHeading = data[self._monidx_apHeading]
1495
1496 state.apAltitudeHold = (apState&0x04000)!=0
1497 state.apAltitude = data[self._monidx_apAltitude]
1498
1499 state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
1500
1501 state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
1502 data[self._monidx_surfaceHeat]!=0 or \
1503 data[self._monidx_propHeat]!=0
1504
1505 return state
1506
1507#------------------------------------------------------------------------------
1508
1509class GenericAircraftModel(AircraftModel):
1510 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1511 values and some other common parameters in a generic way."""
1512
1513 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1514 """Construct the generic aircraft model with the given data.
1515
1516 flapsNotches is an array of how much degrees the individual flaps
1517 notches mean.
1518
1519 fuelTanks is an array of const.FUELTANK_XXX constants about the
1520 aircraft's fuel tanks. They will be converted to offsets.
1521
1522 numEngines is the number of engines the aircraft has.
1523
1524 isN1 determines if the engines have an N1 value or an RPM value
1525 (e.g. pistons)."""
1526 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1527
1528 self._fuelTanks = fuelTanks
1529 self._fuelTankCapacities = [1.0] * len(fuelTanks)
1530 self._fuelIndex = None
1531 self._numEngines = numEngines
1532 self._engineStartIndex = None
1533 self._isN1 = isN1
1534
1535 def doesHandle(self, aircraft, aircraftInfo):
1536 """Determine if the model handles the given aircraft name.
1537
1538 This implementation returns True."""
1539 return True
1540
1541 def addMonitoringData(self, data, fsType):
1542 """Add the model-specific monitoring data to the given array."""
1543 super(GenericAircraftModel, self).addMonitoringData(data, fsType)
1544
1545 self._fuelIndex = self._addFuelData(data)
1546
1547 self._engineStartIndex = len(data)
1548 if self._isN1:
1549 self._addDatarefWithIndexMember(data,
1550 "sim/flightmodel/engine/ENGN_N1_",
1551 (TYPE_FLOAT_ARRAY,
1552 self._numEngines))
1553 else:
1554 self._addDatarefWithIndexMember(data,
1555 "sim/flightmodel/engine/POINT_tacrad",
1556 (TYPE_FLOAT_ARRAY,
1557 self._numEngines))
1558
1559 self._addDatarefWithIndexMember(data,
1560 "sim/flightmodel/engine/ENGN_propmode",
1561 (TYPE_INT_ARRAY, self._numEngines))
1562
1563 def getAircraftState(self, aircraft, timestamp, data):
1564 """Get the aircraft state.
1565
1566 Get it from the parent, and then add the data about the fuel levels and
1567 the engine parameters."""
1568 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1569 timestamp,
1570 data)
1571
1572 state.fuel = []
1573 state.totalFuel = 0.0
1574
1575 fuelAmounts = data[self._fuelIndex]
1576 for i in range(0, len(self._fuelTanks)):
1577 amount = fuelAmounts[i]
1578 state.fuel.append((self._fuelTanks[i], amount))
1579 state.totalFuel += amount
1580
1581 power = data[self._engineStartIndex]
1582
1583 state.n1 = power[:] if self._isN1 else None
1584 state.rpm = None if self._isN1 else power[:]
1585
1586 propMode = data[self._engineStartIndex+1]
1587 state.reverser = [mode == 3 for mode in propMode]
1588
1589 return state
1590
1591 def getFuel(self, handler, callback):
1592 """Get the fuel information for this model.
1593
1594 See Simulator.getFuel for more information. This
1595 implementation simply queries the fuel tanks given to the
1596 constructor."""
1597 data = []
1598 self._addFuelData(data)
1599 data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
1600 data.append( ("sim/aircraft/overflow/acf_tank_rat",
1601 (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
1602
1603 handler.requestRead(data, self._handleFuelRetrieved,
1604 extra = callback)
1605
1606 def setFuelLevel(self, handler, levels):
1607 """Set the fuel level.
1608
1609 See the description of Simulator.setFuelLevel. This
1610 implementation simply sets the fuel tanks as given."""
1611 data = []
1612 for (tank, level) in levels:
1613 try:
1614 index = self._fuelTanks.index(tank)
1615 data.append( ("sim/flightmodel/weight/m_fuel",
1616 (TYPE_FLOAT_ARRAY, 1, index),
1617 [level * self._fuelTankCapacities[index]]) )
1618 except:
1619 print "xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
1620 (tank,)
1621
1622 handler.requestWrite(data, self._handleFuelWritten)
1623
1624 def _addFuelData(self, data):
1625 """Add the fuel offsets to the given data array.
1626
1627 Returns the index of the first fuel tank's data."""
1628 fuelStartIndex = len(data)
1629 data.append( ("sim/flightmodel/weight/m_fuel",
1630 (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
1631
1632 return fuelStartIndex
1633
1634 def _convertFuelData(self, data, index = 0, addCapacities = False):
1635 """Convert the given data into a fuel info list.
1636
1637 The list consists of two or three-tuples of the following
1638 items:
1639 - the fuel tank ID,
1640 - the amount of the fuel in kg,
1641 - if addCapacities is True, the total capacity of the tank."""
1642 fuelWeight = data[index] / 256.0
1643 index += 1
1644
1645 result = []
1646 totalFuel = 0
1647 for fuelTank in self._fuelTanks:
1648 capacity = data[index+1] * fuelWeight * const.LBSTOKG
1649 if capacity>=1.0:
1650 amount = data[index] * capacity / 128.0 / 65536.0
1651
1652 result.append( (fuelTank, amount, capacity) if addCapacities
1653 else (fuelTank, amount))
1654 totalFuel += amount
1655 index += 2
1656
1657 return (result, totalFuel)
1658
1659 def _handleFuelRetrieved(self, data, callback):
1660 """Callback for a fuel retrieval request."""
1661 result = []
1662 totalCapacity = data[1]
1663 for index in range(0, len(self._fuelTanks)):
1664 amount = data[0][index]
1665 capacity = data[2][index] * totalCapacity
1666 self._fuelTankCapacities[index] = capacity
1667 result.append( (self._fuelTanks[index], amount, capacity) )
1668
1669 callback(result)
1670
1671 def _handleFuelWritten(self, success, extra):
1672 """Callback for a fuel setting request."""
1673 pass
1674
1675#------------------------------------------------------------------------------
1676
1677class GenericModel(GenericAircraftModel):
1678 """Generic aircraft model for an unknown type."""
1679 def __init__(self):
1680 """Construct the model."""
1681 super(GenericModel, self). \
1682 __init__(flapsNotches = [0, 10, 20, 30],
1683 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1684 numEngines = 2)
1685
1686 @property
1687 def name(self):
1688 """Get the name for this aircraft model."""
1689 return "X-Plane/Generic"
1690
1691#------------------------------------------------------------------------------
1692
1693class B737Model(GenericAircraftModel):
1694 """Generic model for the Boeing 737 Classing and NG aircraft."""
1695 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1696
1697 def __init__(self):
1698 """Construct the model."""
1699 super(B737Model, self). \
1700 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1701 fuelTanks = B737Model.fuelTanks,
1702 numEngines = 2)
1703
1704 @property
1705 def name(self):
1706 """Get the name for this aircraft model."""
1707 return "X-Plane/Generic Boeing 737"
1708
1709#------------------------------------------------------------------------------
1710
1711class B767Model(GenericAircraftModel):
1712 """Generic model for the Boeing 767 aircraft."""
1713 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1714
1715 def __init__(self):
1716 """Construct the model."""
1717 super(B767Model, self). \
1718 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1719 fuelTanks = B767Model.fuelTanks,
1720 numEngines = 2)
1721
1722 @property
1723 def name(self):
1724 """Get the name for this aircraft model."""
1725 return "X-Plane/Generic Boeing 767"
1726
1727#------------------------------------------------------------------------------
1728
1729class DH8DModel(GenericAircraftModel):
1730 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1731 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1732
1733 def __init__(self):
1734 """Construct the model."""
1735 super(DH8DModel, self). \
1736 __init__(flapsNotches = [0, 5, 10, 15, 35],
1737 fuelTanks = DH8DModel.fuelTanks,
1738 numEngines = 2)
1739
1740 @property
1741 def name(self):
1742 """Get the name for this aircraft model."""
1743 return "X-Plane/Generic Bombardier Dash 8-Q400"
1744
1745#------------------------------------------------------------------------------
1746
1747class FJSDH8DModel(DH8DModel):
1748 """Model handler for the FlyJSim Dash 8-Q400."""
1749 @staticmethod
1750 def doesHandle(aircraft, (tailnum, author, description, notes,
1751 icao, liveryPath)):
1752 """Determine if this model handler handles the aircraft with the given
1753 name."""
1754 return aircraft.type==const.AIRCRAFT_DH8D and \
1755 description.find("Dash 8 Q400")!=-1 and \
1756 ((author in ["2012", "2013"] and tailnum=="N62890") or \
1757 author.find("Jack Skieczius")!=-1)
1758
1759 @property
1760 def name(self):
1761 """Get the name for this aircraft model."""
1762 return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
1763
1764 def addMonitoringData(self, data, fsType):
1765 """Add the model-specific monitoring data to the given array."""
1766 super(FJSDH8DModel, self).addMonitoringData(data, fsType)
1767
1768 self._speedBrakeIndex = len(data)
1769 self._addDatarefWithIndexMember(data,
1770 "sim/flightmodel2/wing/speedbrake1_deg",
1771 (TYPE_FLOAT_ARRAY, 2))
1772 self._addDatarefWithIndexMember(data,
1773 "sim/flightmodel2/wing/speedbrake2_deg",
1774 (TYPE_FLOAT_ARRAY, 2))
1775
1776
1777 def getAircraftState(self, aircraft, timestamp, data):
1778 """Get the aircraft state.
1779
1780 Get it from the parent, and then invert the pitot heat state."""
1781 state = super(FJSDH8DModel, self).getAircraftState(aircraft,
1782 timestamp,
1783 data)
1784 state.antiCollisionLightsOn = \
1785 state.antiCollisionLightsOn or state.strobeLightsOn
1786 state.cog = (state.cog / 0.0254 + 21.504) / 94.512
1787
1788 # It seems that N1 does not always go down to 0 properly
1789 # (maybe due to winds?)
1790 state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
1791
1792 state.spoilersExtension = \
1793 sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
1794
1795 return state
1796
1797#------------------------------------------------------------------------------
1798
1799class CRJ2Model(GenericAircraftModel):
1800 """Generic model for the Bombardier CRJ-200 aircraft."""
1801 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1802
1803 def __init__(self):
1804 """Construct the model."""
1805 super(CRJ2Model, self). \
1806 __init__(flapsNotches = [0, 8, 20, 30, 45],
1807 fuelTanks = CRJ2Model.fuelTanks,
1808 numEngines = 2)
1809
1810 @property
1811 def name(self):
1812 """Get the name for this aircraft model."""
1813 return "X-Plane/Generic Bombardier CRJ-200"
1814
1815#------------------------------------------------------------------------------
1816
1817class F70Model(GenericAircraftModel):
1818 """Generic model for the Fokker F70 aircraft."""
1819 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
1820
1821 def __init__(self):
1822 """Construct the model."""
1823 super(F70Model, self). \
1824 __init__(flapsNotches = [0, 8, 15, 25, 42],
1825 fuelTanks = F70Model.fuelTanks,
1826 numEngines = 2)
1827
1828 @property
1829 def name(self):
1830 """Get the name for this aircraft model."""
1831 return "X-Plane/Generic Fokker 70"
1832
1833#------------------------------------------------------------------------------
1834
1835class DC3Model(GenericAircraftModel):
1836 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
1837 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
1838 const.FUELTANK_RIGHT]
1839 # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
1840 # const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
1841
1842 def __init__(self):
1843 """Construct the model."""
1844 super(DC3Model, self). \
1845 __init__(flapsNotches = [0, 15, 30, 45],
1846 fuelTanks = DC3Model.fuelTanks,
1847 numEngines = 2, isN1 = False)
1848 self._leftLevel = 0.0
1849 self._rightLevel = 0.0
1850
1851 @property
1852 def name(self):
1853 """Get the name for this aircraft model."""
1854 return "X-Plane/Generic Lisunov Li-2 (DC-3)"
1855
1856#------------------------------------------------------------------------------
1857
1858class T134Model(GenericAircraftModel):
1859 """Generic model for the Tupolev Tu-134 aircraft."""
1860 fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1861 const.FUELTANK_LEFT_AUX,
1862 const.FUELTANK_CENTRE,
1863 const.FUELTANK_RIGHT_AUX,
1864 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1865
1866 def __init__(self):
1867 """Construct the model."""
1868 super(T134Model, self). \
1869 __init__(flapsNotches = [0, 10, 20, 30],
1870 fuelTanks = T134Model.fuelTanks,
1871 numEngines = 2)
1872
1873 @property
1874 def name(self):
1875 """Get the name for this aircraft model."""
1876 return "X-Plane/Generic Tupolev Tu-134"
1877
1878#------------------------------------------------------------------------------
1879
1880class T154Model(GenericAircraftModel):
1881 """Generic model for the Tupolev Tu-154 aircraft."""
1882 fuelTanks = [const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
1883 const.FUELTANK_RIGHT, const.FUELTANK_LEFT,
1884 const.FUELTANK_RIGHT_AUX, const.FUELTANK_LEFT_AUX]
1885
1886 def __init__(self):
1887 """Construct the model."""
1888 super(T154Model, self). \
1889 __init__(flapsNotches = [0, 15, 28, 45],
1890 fuelTanks = T154Model.fuelTanks,
1891 numEngines = 3)
1892
1893 @property
1894 def name(self):
1895 """Get the name for this aircraft model."""
1896 return "X-Plane/Generic Tupolev Tu-154"
1897
1898 def getAircraftState(self, aircraft, timestamp, data):
1899 """Get an aircraft state object for the given monitoring data.
1900
1901 This removes the reverser value for the middle engine."""
1902 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
1903 del state.reverser[1]
1904 return state
1905
1906#------------------------------------------------------------------------------
1907
1908class FelisT154Model(T154Model):
1909 """Model for Felis' Tupolev Tu-154-M aircraft."""
1910 @staticmethod
1911 def doesHandle(aircraft, (tailnum, author, description, notes,
1912 icao, liveryPath)):
1913 """Determine if this model handler handles the aircraft with the given
1914 name."""
1915 return aircraft.type==const.AIRCRAFT_T154 and \
1916 author.find("Felis")!=-1 and \
1917 description.find("Tu154M")!=-1
1918
1919 def __init__(self):
1920 """Construct the model."""
1921 super(T154Model, self). \
1922 __init__(flapsNotches = [0, 15, 28, 36, 45],
1923 fuelTanks = T154Model.fuelTanks,
1924 numEngines = 3)
1925
1926 @property
1927 def name(self):
1928 """Get the name for this aircraft model."""
1929 return "X-Plane/Felis Tupolev Tu-154-M"
1930
1931#------------------------------------------------------------------------------
1932
1933class YK40Model(GenericAircraftModel):
1934 """Generic model for the Yakovlev Yak-40 aircraft."""
1935 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
1936
1937 def __init__(self):
1938 """Construct the model."""
1939 super(YK40Model, self). \
1940 __init__(flapsNotches = [0, 20, 35],
1941 fuelTanks = YK40Model.fuelTanks,
1942 numEngines = 2)
1943
1944 @property
1945 def name(self):
1946 """Get the name for this aircraft model."""
1947 return "X-Plane/Generic Yakovlev Yak-40"
1948
1949#------------------------------------------------------------------------------
1950
1951_genericModels = { const.AIRCRAFT_B736 : B737Model,
1952 const.AIRCRAFT_B737 : B737Model,
1953 const.AIRCRAFT_B738 : B737Model,
1954 const.AIRCRAFT_B738C : B737Model,
1955 const.AIRCRAFT_B732 : B737Model,
1956 const.AIRCRAFT_B733 : B737Model,
1957 const.AIRCRAFT_B734 : B737Model,
1958 const.AIRCRAFT_B735 : B737Model,
1959 const.AIRCRAFT_DH8D : DH8DModel,
1960 const.AIRCRAFT_B762 : B767Model,
1961 const.AIRCRAFT_B763 : B767Model,
1962 const.AIRCRAFT_CRJ2 : CRJ2Model,
1963 const.AIRCRAFT_F70 : F70Model,
1964 const.AIRCRAFT_DC3 : DC3Model,
1965 const.AIRCRAFT_T134 : T134Model,
1966 const.AIRCRAFT_T154 : T154Model,
1967 const.AIRCRAFT_YK40 : YK40Model }
1968
1969#------------------------------------------------------------------------------
1970
1971AircraftModel.registerSpecial(FJSDH8DModel)
1972AircraftModel.registerSpecial(FelisT154Model)
1973
1974#------------------------------------------------------------------------------
1975
1976# if __name__ == "__main__":
1977# class ConnectionListener:
1978# def connected(self, fsType, descriptor):
1979# """Called when a connection has been established to the flight
1980# simulator of the given type."""
1981# print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
1982
1983# def connectionFailed(self):
1984# """Called when the connection could not be established."""
1985# print "fs.ConnectionListener.connectionFailed"
1986
1987# def disconnected(self):
1988# """Called when a connection to the flight simulator has been broken."""
1989# print "fs.ConnectionListener.disconnected"
1990
1991# class Config:
1992# def __init__(self):
1993# self.onlineACARS = False
1994# self.realIASSmoothingLength = 2
1995# self.realVSSmoothingLength = 2
1996# self.enableSounds = False
1997# self.usingFS2Crew = False
1998
1999# def isMessageTypeFS(self, type):
2000# return True
2001
2002
2003
2004# class GUI:
2005# def __init__(self):
2006# self.config = Config()
2007# self.entranceExam = False
2008# self.zfw = 30000.0
2009
2010# def resetFlightStatus(self):
2011# pass
2012
2013# def setRating(self, value):
2014# pass
2015
2016# def insertFlightLogLine(self, index, ts, text, isFault):
2017# pass
2018
2019# def setStage(self, stage):
2020# pass
2021
2022
2023# from i18n import setLanguage
2024
2025# setLanguage("/home/vi/munka/repules/mlx", "en")
2026
2027# from logger import Logger
2028# from flight import Flight
2029# from acft import DH8D
2030
2031# gui = GUI()
2032
2033# logger = Logger(gui)
2034
2035# flight = Flight(logger, gui)
2036# acft = DH8D(flight)
2037
2038# Watchdog()
2039
2040# connectionListener = ConnectionListener()
2041# simulator = Simulator(connectionListener, connectAttempts = 3)
2042
2043# simulator.connect(acft)
2044
2045# time.sleep(2)
2046
2047# simulator.startMonitoring()
2048
2049# simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
2050
2051# time.sleep(4)
2052
2053# 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)
2054# #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)
2055# #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)
2056
2057# time.sleep(30)
2058
2059# simulator.sendMessage("[MLX] Hello", duration = 3)
2060
2061# time.sleep(10)
Note: See TracBrowser for help on using the repository browser.