source: src/mlx/xplane.py@ 904:be1c17423472

version_0.39_maint
Last change on this file since 904:be1c17423472 was 903:521a565fc5cc, checked in by István Váradi <ivaradi@…>, 7 years ago

Some extra printous related to the flaps on X-Plane

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