source: src/mlx/fsuipc.py@ 152:f0701a18628a

Last change on this file since 152:f0701a18628a was 152:f0701a18628a, checked in by István Váradi <ivaradi@…>, 13 years ago

Made the sending of the closing message more reliable

File size: 55.9 KB
Line 
1# Module handling the connection to FSUIPC
2
3#------------------------------------------------------------------------------
4
5import fs
6import const
7import util
8import acft
9
10import threading
11import os
12import time
13import calendar
14import sys
15import codecs
16
17if os.name == "nt":
18 import pyuipc
19else:
20 import pyuipc_sim as pyuipc
21
22#------------------------------------------------------------------------------
23
24# The mapping of tank types to FSUIPC offsets
25_tank2offset = { const.FUELTANK_CENTRE : 0x0b74,
26 const.FUELTANK_LEFT : 0x0b7c,
27 const.FUELTANK_RIGHT : 0x0b94,
28 const.FUELTANK_LEFT_AUX : 0x0b84,
29 const.FUELTANK_RIGHT_AUX : 0x0b9c,
30 const.FUELTANK_LEFT_TIP : 0x0b8c,
31 const.FUELTANK_RIGHT_TIP : 0x0ba4,
32 const.FUELTANK_EXTERNAL1 : 0x1254,
33 const.FUELTANK_EXTERNAL2 : 0x125c,
34 const.FUELTANK_CENTRE2 : 0x1244 }
35
36#------------------------------------------------------------------------------
37
38class Handler(threading.Thread):
39 """The thread to handle the FSUIPC requests."""
40 @staticmethod
41 def fsuipc2VS(data):
42 """Convert the given vertical speed data read from FSUIPC into feet/min."""
43 return data*60.0/const.FEETTOMETRES/256.0
44
45 @staticmethod
46 def fsuipc2radioAltitude(data):
47 """Convert the given radio altitude data read from FSUIPC into feet."""
48 return data/const.FEETTOMETRES/65536.0
49
50 @staticmethod
51 def fsuipc2Degrees(data):
52 """Convert the given data into degrees."""
53 return data * 360.0 / 65536.0 / 65536.0
54
55 @staticmethod
56 def fsuipc2PositiveDegrees(data):
57 """Convert the given data into positive degrees."""
58 degrees = Handler.fsuipc2Degrees(data)
59 if degrees<0.0: degrees += 360.0
60 return degrees
61
62 @staticmethod
63 def fsuipc2IAS(data):
64 """Convert the given data into indicated airspeed."""
65 return data / 128.0
66
67 @staticmethod
68 def _callSafe(fun):
69 """Call the given function and swallow any exceptions."""
70 try:
71 return fun()
72 except Exception, e:
73 print >> sys.stderr, str(e)
74 return None
75
76 # The number of times a read is attempted
77 NUM_READATTEMPTS = 3
78
79 # The number of connection attempts
80 NUM_CONNECTATTEMPTS = 3
81
82 # The interval between successive connect attempts
83 CONNECT_INTERVAL = 0.25
84
85 @staticmethod
86 def _performRead(data, callback, extra, validator):
87 """Perform a read request.
88
89 If there is a validator, that will be called with the return values,
90 and if the values are wrong, the request is retried at most a certain
91 number of times.
92
93 Return True if the request has succeeded, False if validation has
94 failed during all attempts. An exception may also be thrown if there is
95 some lower-level communication problem."""
96 attemptsLeft = Handler.NUM_READATTEMPTS
97 while attemptsLeft>0:
98 values = pyuipc.read(data)
99 if validator is None or \
100 Handler._callSafe(lambda: validator(values, extra)):
101 Handler._callSafe(lambda: callback(values, extra))
102 return True
103 else:
104 attemptsLeft -= 1
105 return False
106
107 class Request(object):
108 """A simple, one-shot request."""
109 def __init__(self, forWrite, data, callback, extra, validator = None):
110 """Construct the request."""
111 self._forWrite = forWrite
112 self._data = data
113 self._callback = callback
114 self._extra = extra
115 self._validator = validator
116
117 def process(self, time):
118 """Process the request.
119
120 Return True if the request has succeeded, False if data validation
121 has failed for a reading request. An exception may also be thrown
122 if there is some lower-level communication problem."""
123 if self._forWrite:
124 pyuipc.write(self._data)
125 Handler._callSafe(lambda: self._callback(True, self._extra))
126 return True
127 else:
128 return Handler._performRead(self._data, self._callback,
129 self._extra, self._validator)
130
131 def fail(self):
132 """Handle the failure of this request."""
133 if self._forWrite:
134 Handler._callSafe(lambda: self._callback(False, self._extra))
135 else:
136 Handler._callSafe(lambda: self._callback(None, self._extra))
137
138 class PeriodicRequest(object):
139 """A periodic request."""
140 def __init__(self, id, period, data, callback, extra, validator):
141 """Construct the periodic request."""
142 self._id = id
143 self._period = period
144 self._nextFire = time.time() + period
145 self._data = data
146 self._preparedData = None
147 self._callback = callback
148 self._extra = extra
149 self._validator = validator
150
151 @property
152 def id(self):
153 """Get the ID of this periodic request."""
154 return self._id
155
156 @property
157 def nextFire(self):
158 """Get the next firing time."""
159 return self._nextFire
160
161 def process(self, time):
162 """Check if this request should be executed, and if so, do so.
163
164 time is the time at which the request is being executed. If this
165 function is called too early, nothing is done, and True is
166 returned.
167
168 Return True if the request has succeeded, False if data validation
169 has failed. An exception may also be thrown if there is some
170 lower-level communication problem."""
171 if time<self._nextFire:
172 return True
173
174 if self._preparedData is None:
175 self._preparedData = pyuipc.prepare_data(self._data)
176 self._data = None
177
178 isOK = Handler._performRead(self._preparedData, self._callback,
179 self._extra, self._validator)
180
181 if isOK:
182 while self._nextFire <= time:
183 self._nextFire += self._period
184
185 return isOK
186
187 def fail(self):
188 """Handle the failure of this request."""
189 pass
190
191 def __cmp__(self, other):
192 """Compare two periodic requests. They are ordered by their next
193 firing times."""
194 return cmp(self._nextFire, other._nextFire)
195
196 def __init__(self, connectionListener,
197 connectAttempts = -1, connectInterval = 0.2):
198 """Construct the handler with the given connection listener."""
199 threading.Thread.__init__(self)
200
201 self._connectionListener = connectionListener
202 self._connectAttempts = connectAttempts
203 self._connectInterval = connectInterval
204
205 self._requestCondition = threading.Condition()
206 self._connectionRequested = False
207 self._connected = False
208
209 self._requests = []
210 self._nextPeriodicID = 1
211 self._periodicRequests = []
212
213 self.daemon = True
214
215 def requestRead(self, data, callback, extra = None, validator = None):
216 """Request the reading of some data.
217
218 data is a list of tuples of the following items:
219 - the offset of the data as an integer
220 - the type letter of the data as a string
221
222 callback is a function that receives two pieces of data:
223 - the values retrieved or None on error
224 - the extra parameter
225
226 It will be called in the handler's thread!
227 """
228 with self._requestCondition:
229 self._requests.append(Handler.Request(False, data, callback, extra,
230 validator))
231 self._requestCondition.notify()
232
233 def requestWrite(self, data, callback, extra = None):
234 """Request the writing of some data.
235
236 data is a list of tuples of the following items:
237 - the offset of the data as an integer
238 - the type letter of the data as a string
239 - the data to write
240
241 callback is a function that receives two pieces of data:
242 - a boolean indicating if writing was successful
243 - the extra data
244 It will be called in the handler's thread!
245 """
246 with self._requestCondition:
247 request = Handler.Request(True, data, callback, extra)
248 #print "fsuipc.Handler.requestWrite", request
249 self._requests.append(request)
250 self._requestCondition.notify()
251
252 @staticmethod
253 def _readWriteCallback(data, extra):
254 """Callback for the read() and write() calls below."""
255 extra.append(data)
256 with extra[0] as condition:
257 condition.notify()
258
259 def requestPeriodicRead(self, period, data, callback, extra = None,
260 validator = None):
261 """Request a periodic read of data.
262
263 period is a floating point number with the period in seconds.
264
265 This function returns an identifier which can be used to cancel the
266 request."""
267 with self._requestCondition:
268 id = self._nextPeriodicID
269 self._nextPeriodicID += 1
270 request = Handler.PeriodicRequest(id, period, data, callback,
271 extra, validator)
272 self._periodicRequests.append(request)
273 self._requestCondition.notify()
274 return id
275
276 def clearPeriodic(self, id):
277 """Clear the periodic request with the given ID."""
278 with self._requestCondition:
279 for i in range(0, len(self._periodicRequests)):
280 if self._periodicRequests[i].id==id:
281 del self._periodicRequests[i]
282 return True
283 return False
284
285 def connect(self):
286 """Initiate the connection to the flight simulator."""
287 with self._requestCondition:
288 if not self._connectionRequested:
289 self._connectionRequested = True
290 self._requestCondition.notify()
291
292 def disconnect(self):
293 """Disconnect from the flight simulator."""
294 with self._requestCondition:
295 self._requests = []
296 if self._connectionRequested:
297 self._connectionRequested = False
298 self._requestCondition.notify()
299
300 def clearRequests(self):
301 """Clear the outstanding one-shot requests."""
302 with self._requestCondition:
303 self._requests = []
304
305 def run(self):
306 """Perform the operation of the thread."""
307 while True:
308 self._waitConnectionRequest()
309
310 if self._connect():
311 self._handleConnection()
312
313 self._disconnect()
314
315 def _waitConnectionRequest(self):
316 """Wait for a connection request to arrive."""
317 with self._requestCondition:
318 while not self._connectionRequested:
319 self._requestCondition.wait()
320
321 def _connect(self, autoReconnection = False):
322 """Try to connect to the flight simulator via FSUIPC
323
324 Returns True if the connection has been established, False if it was
325 not due to no longer requested.
326 """
327 attempts = 0
328 while self._connectionRequested:
329 try:
330 attempts += 1
331 pyuipc.open(pyuipc.SIM_ANY)
332 description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
333 (pyuipc.fsuipc_version, pyuipc.lib_version,
334 pyuipc.fs_version)
335 if not autoReconnection:
336 Handler._callSafe(lambda:
337 self._connectionListener.connected(const.SIM_MSFS9,
338 description))
339 self._connected = True
340 return True
341 except Exception, e:
342 print "fsuipc.Handler._connect: connection failed: " + str(e)
343 if attempts<self.NUM_CONNECTATTEMPTS:
344 time.sleep(self.CONNECT_INTERVAL)
345 else:
346 self._connectionRequested = False
347 if autoReconnection:
348 Handler._callSafe(lambda:
349 self._connectionListener.disconnected())
350 else:
351 Handler._callSafe(lambda:
352 self._connectionListener.connectionFailed())
353
354 return False
355
356 def _handleConnection(self):
357 """Handle a living connection."""
358 with self._requestCondition:
359 while self._connectionRequested:
360 self._processRequests()
361 self._waitRequest()
362
363 def _waitRequest(self):
364 """Wait for the time of the next request.
365
366 Returns also, if the connection is no longer requested.
367
368 Should be called with the request condition lock held."""
369 while self._connectionRequested:
370 timeout = None
371 if self._periodicRequests:
372 self._periodicRequests.sort()
373 timeout = self._periodicRequests[0].nextFire - time.time()
374
375 if self._requests or \
376 (timeout is not None and timeout <= 0.0):
377 return
378
379 self._requestCondition.wait(timeout)
380
381 def _disconnect(self):
382 """Disconnect from the flight simulator."""
383 print "fsuipc.Handler._disconnect"
384 if self._connected:
385 pyuipc.close()
386 self._connected = False
387
388 def _processRequest(self, request, time):
389 """Process the given request.
390
391 If an exception occurs or invalid data is read too many times, we try
392 to reconnect.
393
394 This function returns only if the request has succeeded, or if a
395 connection is no longer requested.
396
397 This function is called with the request lock held, but is relased
398 whole processing the request and reconnecting."""
399 self._requestCondition.release()
400
401 #print "fsuipc.Handler._processRequest", request
402
403 needReconnect = False
404 try:
405 try:
406 if not request.process(time):
407 print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
408 needReconnect = True
409 except Exception as e:
410 print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
411 str(e) + "), reconnecting."
412 needReconnect = True
413
414 if needReconnect:
415 with self._requestCondition:
416 self._requests.insert(0, request)
417 self._disconnect()
418 self._connect(autoReconnection = True)
419 finally:
420 self._requestCondition.acquire()
421
422 def _processRequests(self):
423 """Process any pending requests.
424
425 Will be called with the request lock held."""
426 while self._connectionRequested and self._periodicRequests:
427 self._periodicRequests.sort()
428 request = self._periodicRequests[0]
429
430 t = time.time()
431
432 if request.nextFire>t:
433 break
434
435 self._processRequest(request, t)
436
437 while self._connectionRequested and self._requests:
438 request = self._requests[0]
439 del self._requests[0]
440
441 self._processRequest(request, None)
442
443 return self._connectionRequested
444
445#------------------------------------------------------------------------------
446
447class Simulator(object):
448 """The simulator class representing the interface to the flight simulator
449 via FSUIPC."""
450 # The basic data that should be queried all the time once we are connected
451 timeData = [ (0x0240, "H"), # Year
452 (0x023e, "H"), # Number of day in year
453 (0x023b, "b"), # UTC hour
454 (0x023c, "b"), # UTC minute
455 (0x023a, "b") ] # seconds
456
457 normalData = timeData + \
458 [ (0x3d00, -256), # The name of the current aircraft
459 (0x3c00, -256), # The path of the current AIR file
460 (0x1274, "h") ] # Text display mode
461
462 flareData1 = [ (0x023a, "b"), # Seconds of time
463 (0x31e4, "d"), # Radio altitude
464 (0x02c8, "d") ] # Vertical speed
465
466 flareStartData = [ (0x0e90, "H"), # Ambient wind speed
467 (0x0e92, "H"), # Ambient wind direction
468 (0x0e8a, "H") ] # Visibility
469
470 flareData2 = [ (0x023a, "b"), # Seconds of time
471 (0x0366, "H"), # On the ground
472 (0x02c8, "d"), # Vertical speed
473 (0x030c, "d"), # Touch-down rate
474 (0x02bc, "d"), # IAS
475 (0x0578, "d"), # Pitch
476 (0x057c, "d"), # Bank
477 (0x0580, "d") ] # Heading
478
479 TIME_SYNC_INTERVAL = 3.0
480
481 @staticmethod
482 def _getTimestamp(data):
483 """Convert the given data into a timestamp."""
484 timestamp = calendar.timegm(time.struct_time([data[0],
485 1, 1, 0, 0, 0, -1, 1, 0]))
486 timestamp += data[1] * 24 * 3600
487 timestamp += data[2] * 3600
488 timestamp += data[3] * 60
489 timestamp += data[4]
490
491 return timestamp
492
493 def __init__(self, connectionListener, connectAttempts = -1,
494 connectInterval = 0.2):
495 """Construct the simulator.
496
497 The aircraft object passed must provide the following members:
498 - type: one of the AIRCRAFT_XXX constants from const.py
499 - modelChanged(aircraftName, modelName): called when the model handling
500 the aircraft has changed.
501 - handleState(aircraftState): handle the given state.
502 - flareStarted(windSpeed, windDirection, visibility, flareStart,
503 flareStartFS): called when the flare has
504 started. windSpeed is in knots, windDirection is in degrees and
505 visibility is in metres. flareStart and flareStartFS are two time
506 values expressed in seconds that can be used to calculate the flare
507 time.
508 - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
509 ias, pitch, bank, heading): called when the flare has
510 finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
511 are the two time values corresponding to the touchdown time. tdRate is
512 the touch-down rate, tdRateCalculatedBySim indicates if the data comes
513 from the simulator or was calculated by the adapter. The other data
514 are self-explanatory and expressed in their 'natural' units."""
515 self._aircraft = None
516
517 self._handler = Handler(connectionListener,
518 connectAttempts = connectAttempts,
519 connectInterval = connectInterval)
520 self._handler.start()
521
522 self._scroll = False
523
524 self._syncTime = False
525 self._nextSyncTime = -1
526
527 self._normalRequestID = None
528
529 self._monitoringRequested = False
530 self._monitoring = False
531
532 self._aircraftName = None
533 self._aircraftModel = None
534
535 self._flareRequestID = None
536 self._flareRates = []
537 self._flareStart = None
538 self._flareStartFS = None
539
540 self._latin1decoder = codecs.getdecoder("iso-8859-1")
541
542 def connect(self, aircraft):
543 """Initiate a connection to the simulator."""
544 self._aircraft = aircraft
545 self._aircraftName = None
546 self._aircraftModel = None
547 self._handler.connect()
548 if self._normalRequestID is None:
549 self._nextSyncTime = -1
550 self._startDefaultNormal()
551
552 def reconnect(self):
553 """Initiate a reconnection to the simulator.
554
555 It does not reset already set up data, just calls connect() on the
556 handler."""
557 self._handler.connect()
558
559 def requestZFW(self, callback):
560 """Send a request for the ZFW."""
561 self._handler.requestRead([(0x3bfc, "d")], self._handleZFW, extra = callback)
562
563 def requestWeights(self, callback):
564 """Request the following weights: DOW, ZFW, payload.
565
566 These values will be passed to the callback function in this order, as
567 separate arguments."""
568 self._handler.requestRead([(0x13fc, "d")], self._handlePayloadCount,
569 extra = callback)
570
571 def requestTime(self, callback):
572 """Request the time from the simulator."""
573 self._handler.requestRead(Simulator.timeData, self._handleTime,
574 extra = callback)
575
576 def startMonitoring(self):
577 """Start the periodic monitoring of the aircraft and pass the resulting
578 state to the aircraft object periodically."""
579 assert not self._monitoringRequested
580 self._monitoringRequested = True
581
582 def stopMonitoring(self):
583 """Stop the periodic monitoring of the aircraft."""
584 assert self._monitoringRequested
585 self._monitoringRequested = False
586
587 def startFlare(self):
588 """Start monitoring the flare time.
589
590 At present it is assumed to be called from the FSUIPC thread, hence no
591 protection."""
592 #self._aircraft.logger.debug("startFlare")
593 if self._flareRequestID is None:
594 self._flareRates = []
595 self._flareRequestID = self._handler.requestPeriodicRead(0.1,
596 Simulator.flareData1,
597 self._handleFlare1)
598
599 def cancelFlare(self):
600 """Cancel monitoring the flare time.
601
602 At present it is assumed to be called from the FSUIPC thread, hence no
603 protection."""
604 if self._flareRequestID is not None:
605 self._handler.clearPeriodic(self._flareRequestID)
606 self._flareRequestID = None
607
608 def sendMessage(self, message, duration = 3,
609 _disconnect = False):
610 """Send a message to the pilot via the simulator.
611
612 duration is the number of seconds to keep the message displayed."""
613
614 if self._scroll:
615 if duration==0: duration = -1
616 elif duration == 1: duration = -2
617 else: duration = -duration
618
619 data = [(0x3380, -1 - len(message), message),
620 (0x32fa, 'h', duration)]
621
622 #if _disconnect:
623 # print "fsuipc.Simulator.sendMessage(disconnect)", message
624
625 self._handler.requestWrite(data, self._handleMessageSent,
626 extra = _disconnect)
627
628 def getFuel(self, tanks, callback):
629 """Get the fuel from the given tanks.
630
631 The callback will be called with a list of two-tuples, where the tuples
632 have the following items:
633 - the current weight of the fuel in the tank (in kgs)
634 - the current total capacity of the tank (in kgs)."""
635 data = [(0x0af4, "H")] # Fuel weight
636 for tank in tanks:
637 offset = _tank2offset[tank]
638 data.append( (offset, "u") ) # tank level
639 data.append( (offset+4, "u") ) # tank capacity
640
641 self._handler.requestRead(data, self._handleFuelRetrieved,
642 extra = callback)
643
644 def setFuelLevel(self, levels):
645 """Set the fuel level to the given ones.
646
647 levels is an array of two-tuples, where each tuple consists of the
648 following:
649 - the const.FUELTANK_XXX constant denoting the tank that must be set,
650 - the requested level of the fuel as a floating-point value between 0.0
651 and 1.0."""
652 data = []
653 for (tank, level) in levels:
654 offset = _tank2offset[tank]
655 data.append( (offset, "u", long(level * 128.8 * 65536.0)) )
656 self._handler.requestWrite(data, self._handleFuelWritten)
657
658 def enableTimeSync(self):
659 """Enable the time synchronization."""
660 self._nextSyncTime = -1
661 self._syncTime = True
662
663 def disableTimeSync(self):
664 """Enable the time synchronization."""
665 self._syncTime = False
666 self._nextSyncTime = -1
667
668 def disconnect(self, closingMessage = None, duration = 3):
669 """Disconnect from the simulator."""
670 assert not self._monitoringRequested
671
672 print "fsuipc.Simulator.disconnect", closingMessage, duration
673
674 self._stopNormal()
675 if closingMessage is None:
676 self._handler.disconnect()
677 else:
678 self.sendMessage(closingMessage, duration = duration,
679 _disconnect = True)
680
681 def _startDefaultNormal(self):
682 """Start the default normal periodic request."""
683 assert self._normalRequestID is None
684 self._normalRequestID = \
685 self._handler.requestPeriodicRead(1.0,
686 Simulator.normalData,
687 self._handleNormal,
688 validator = self._validateNormal)
689
690 def _stopNormal(self):
691 """Stop the normal period request."""
692 assert self._normalRequestID is not None
693 self._handler.clearPeriodic(self._normalRequestID)
694 self._normalRequestID = None
695 self._monitoring = False
696
697 def _validateNormal(self, data, extra):
698 """Validate the normal data."""
699 return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
700
701 def _handleNormal(self, data, extra):
702 """Handle the reply to the normal request.
703
704 At the beginning the result consists the data for normalData. When
705 monitoring is started, it contains the result also for the
706 aircraft-specific values.
707 """
708 timestamp = Simulator._getTimestamp(data)
709
710 createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
711
712 self._scroll = data[7]!=0
713
714 self._checkTimeSync()
715
716 if self._monitoringRequested and not self._monitoring:
717 self._stopNormal()
718 self._startMonitoring()
719 elif self._monitoring and not self._monitoringRequested:
720 self._stopNormal()
721 self._startDefaultNormal()
722 elif self._monitoring and self._aircraftModel is not None and \
723 not createdNewModel:
724 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
725 timestamp, data)
726 self._aircraft.handleState(aircraftState)
727
728 def _checkTimeSync(self):
729 """Check if we need to synchronize the FS time."""
730 if not self._syncTime: return
731
732 now = time.time()
733 seconds = time.gmtime(now).tm_sec
734
735 if seconds>30 and seconds<59:
736 if self._nextSyncTime > (now - 0.49):
737 return
738
739 self._handler.requestWrite([(0x023a, "b", int(seconds))],
740 self._handleTimeSynced)
741
742 #print "Set the seconds to ", seconds
743
744 if self._nextSyncTime<0:
745 self._nextSyncTime = now
746
747 self._nextSyncTime += Simulator.TIME_SYNC_INTERVAL
748 else:
749 self._nextSyncTime = -1
750
751 def _handleTimeSynced(self, success, extra):
752 """Callback for the time sync result."""
753 pass
754
755 def _setAircraftName(self, timestamp, name, airPath):
756 """Set the name of the aicraft and if it is different from the
757 previous, create a new model for it.
758
759 If so, also notifty the aircraft about the change.
760
761 Return if a new model was created."""
762 aircraftName = (name, airPath)
763 if aircraftName==self._aircraftName:
764 return False
765
766 self._aircraftName = aircraftName
767 needNew = self._aircraftModel is None
768 needNew = needNew or\
769 not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
770 if not needNew:
771 specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
772 needNew = specialModel is not None and \
773 specialModel is not self._aircraftModel.__class__
774
775 if needNew:
776 self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
777
778
779 self._aircraft.modelChanged(timestamp, self._latin1decoder(name)[0],
780 self._aircraftModel.name)
781
782 return needNew
783
784 def _setAircraftModel(self, model):
785 """Set a new aircraft model.
786
787 It will be queried for the data to monitor and the monitoring request
788 will be replaced by a new one."""
789 self._aircraftModel = model
790
791 if self._monitoring:
792 self._stopNormal()
793 self._startMonitoring()
794
795 def _startMonitoring(self):
796 """Start monitoring with the current aircraft model."""
797 data = Simulator.normalData[:]
798 self._aircraftModel.addMonitoringData(data)
799
800 self._normalRequestID = \
801 self._handler.requestPeriodicRead(1.0, data,
802 self._handleNormal,
803 validator = self._validateNormal)
804 self._monitoring = True
805
806 def _addFlareRate(self, data):
807 """Append a flare rate to the list of last rates."""
808 if len(self._flareRates)>=3:
809 del self._flareRates[0]
810 self._flareRates.append(Handler.fsuipc2VS(data))
811
812 def _handleFlare1(self, data, normal):
813 """Handle the first stage of flare monitoring."""
814 #self._aircraft.logger.debug("handleFlare1: " + str(data))
815 if Handler.fsuipc2radioAltitude(data[1])<=50.0:
816 self._flareStart = time.time()
817 self._flareStartFS = data[0]
818 self._handler.clearPeriodic(self._flareRequestID)
819 self._flareRequestID = \
820 self._handler.requestPeriodicRead(0.1,
821 Simulator.flareData2,
822 self._handleFlare2)
823 self._handler.requestRead(Simulator.flareStartData,
824 self._handleFlareStart)
825
826 self._addFlareRate(data[2])
827
828 def _handleFlareStart(self, data, extra):
829 """Handle the data need to notify the aircraft about the starting of
830 the flare."""
831 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
832 if data is not None:
833 windDirection = data[1]*360.0/65536.0
834 if windDirection<0.0: windDirection += 360.0
835 self._aircraft.flareStarted(data[0], windDirection,
836 data[2]*1609.344/100.0,
837 self._flareStart, self._flareStartFS)
838
839 def _handleFlare2(self, data, normal):
840 """Handle the first stage of flare monitoring."""
841 #self._aircraft.logger.debug("handleFlare2: " + str(data))
842 if data[1]!=0:
843 flareEnd = time.time()
844 self._handler.clearPeriodic(self._flareRequestID)
845 self._flareRequestID = None
846
847 flareEndFS = data[0]
848 if flareEndFS<self._flareStartFS:
849 flareEndFS += 60
850
851 tdRate = Handler.fsuipc2VS(data[3])
852 tdRateCalculatedByFS = True
853 if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
854 tdRate = min(self._flareRates)
855 tdRateCalculatedByFS = False
856
857 self._aircraft.flareFinished(flareEnd, flareEndFS,
858 tdRate, tdRateCalculatedByFS,
859 Handler.fsuipc2IAS(data[4]),
860 Handler.fsuipc2Degrees(data[5]),
861 Handler.fsuipc2Degrees(data[6]),
862 Handler.fsuipc2PositiveDegrees(data[7]))
863 else:
864 self._addFlareRate(data[2])
865
866 def _handleZFW(self, data, callback):
867 """Callback for a ZFW retrieval request."""
868 zfw = data[0] * const.LBSTOKG / 256.0
869 callback(zfw)
870
871 def _handleTime(self, data, callback):
872 """Callback for a time retrieval request."""
873 callback(Simulator._getTimestamp(data))
874
875 def _handlePayloadCount(self, data, callback):
876 """Callback for the payload count retrieval request."""
877 payloadCount = data[0]
878 data = [(0x3bfc, "d"), (0x30c0, "f")]
879 for i in range(0, payloadCount):
880 data.append((0x1400 + i*48, "f"))
881
882 self._handler.requestRead(data, self._handleWeights,
883 extra = callback)
884
885 def _handleWeights(self, data, callback):
886 """Callback for the weights retrieval request."""
887 zfw = data[0] * const.LBSTOKG / 256.0
888 grossWeight = data[1] * const.LBSTOKG
889 payload = sum(data[2:]) * const.LBSTOKG
890 dow = zfw - payload
891 callback(dow, payload, zfw, grossWeight)
892
893 def _handleMessageSent(self, success, disconnect):
894 """Callback for a message sending request."""
895 #print "fsuipc.Simulator._handleMessageSent", disconnect
896 if disconnect:
897 self._handler.disconnect()
898
899 def _handleFuelRetrieved(self, data, callback):
900 """Callback for a fuel retrieval request."""
901 fuelWeight = data[0] / 256.0
902 result = []
903 for i in range(1, len(data), 2):
904 capacity = data[i+1] * fuelWeight * const.LBSTOKG
905 amount = data[i] * capacity / 128.0 / 65536.0
906 result.append( (amount, capacity) )
907
908 callback(result)
909
910 def _handleFuelWritten(self, success, extra):
911 """Callback for a fuel setting request."""
912 pass
913
914#------------------------------------------------------------------------------
915
916class AircraftModel(object):
917 """Base class for the aircraft models.
918
919 Aircraft models handle the data arriving from FSUIPC and turn it into an
920 object describing the aircraft's state."""
921 monitoringData = [("paused", 0x0264, "H"),
922 ("latitude", 0x0560, "l"),
923 ("longitude", 0x0568, "l"),
924 ("frozen", 0x3364, "H"),
925 ("replay", 0x0628, "d"),
926 ("slew", 0x05dc, "H"),
927 ("overspeed", 0x036d, "b"),
928 ("stalled", 0x036c, "b"),
929 ("onTheGround", 0x0366, "H"),
930 ("zfw", 0x3bfc, "d"),
931 ("grossWeight", 0x30c0, "f"),
932 ("heading", 0x0580, "d"),
933 ("pitch", 0x0578, "d"),
934 ("bank", 0x057c, "d"),
935 ("ias", 0x02bc, "d"),
936 ("mach", 0x11c6, "H"),
937 ("groundSpeed", 0x02b4, "d"),
938 ("vs", 0x02c8, "d"),
939 ("radioAltitude", 0x31e4, "d"),
940 ("altitude", 0x0570, "l"),
941 ("gLoad", 0x11ba, "H"),
942 ("flapsControl", 0x0bdc, "d"),
943 ("flapsLeft", 0x0be0, "d"),
944 ("flapsRight", 0x0be4, "d"),
945 ("lights", 0x0d0c, "H"),
946 ("pitot", 0x029c, "b"),
947 ("parking", 0x0bc8, "H"),
948 ("noseGear", 0x0bec, "d"),
949 ("spoilersArmed", 0x0bcc, "d"),
950 ("spoilers", 0x0bd0, "d"),
951 ("altimeter", 0x0330, "H"),
952 ("nav1", 0x0350, "H"),
953 ("nav2", 0x0352, "H"),
954 ("squawk", 0x0354, "H"),
955 ("windSpeed", 0x0e90, "H"),
956 ("windDirection", 0x0e92, "H"),
957 ("visibility", 0x0e8a, "H")]
958
959
960 specialModels = []
961
962 @staticmethod
963 def registerSpecial(clazz):
964 """Register the given class as a special model."""
965 AircraftModel.specialModels.append(clazz)
966
967 @staticmethod
968 def findSpecial(aircraft, aircraftName):
969 for specialModel in AircraftModel.specialModels:
970 if specialModel.doesHandle(aircraft, aircraftName):
971 return specialModel
972 return None
973
974 @staticmethod
975 def create(aircraft, aircraftName):
976 """Create the model for the given aircraft name, and notify the
977 aircraft about it."""
978 specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
979 if specialModel is not None:
980 return specialModel()
981 if aircraft.type in _genericModels:
982 return _genericModels[aircraft.type]()
983 else:
984 return GenericModel()
985
986 @staticmethod
987 def convertBCD(data, length):
988 """Convert a data item encoded as BCD into a string of the given number
989 of digits."""
990 bcd = ""
991 for i in range(0, length):
992 digit = chr(ord('0') + (data&0x0f))
993 data >>= 4
994 bcd = digit + bcd
995 return bcd
996
997 @staticmethod
998 def convertFrequency(data):
999 """Convert the given frequency data to a string."""
1000 bcd = AircraftModel.convertBCD(data, 4)
1001 return "1" + bcd[0:2] + "." + bcd[2:4]
1002
1003 def __init__(self, flapsNotches):
1004 """Construct the aircraft model.
1005
1006 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1007 self._flapsNotches = flapsNotches
1008
1009 @property
1010 def name(self):
1011 """Get the name for this aircraft model."""
1012 return "FSUIPC/Generic"
1013
1014 def doesHandle(self, aircraft, aircraftName):
1015 """Determine if the model handles the given aircraft name.
1016
1017 This default implementation returns False."""
1018 return False
1019
1020 def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
1021 """Add the given FSUIPC offset and type to the given array and a member
1022 attribute with the given name."""
1023 dest.append((offset, type))
1024 if attrName is not None:
1025 setattr(self, attrName, len(dest)-1)
1026
1027 def _addDataWithIndexMembers(self, dest, prefix, data):
1028 """Add FSUIPC data to the given array and also corresponding index
1029 member variables with the given prefix.
1030
1031 data is a list of triplets of the following items:
1032 - the name of the data item. The index member variable will have a name
1033 created by prepending the given prefix to this name.
1034 - the FSUIPC offset
1035 - the FSUIPC type
1036
1037 The latter two items will be appended to dest."""
1038 for (name, offset, type) in data:
1039 self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
1040
1041 def addMonitoringData(self, data):
1042 """Add the model-specific monitoring data to the given array."""
1043 self._addDataWithIndexMembers(data, "_monidx_",
1044 AircraftModel.monitoringData)
1045
1046 def getAircraftState(self, aircraft, timestamp, data):
1047 """Get an aircraft state object for the given monitoring data."""
1048 state = fs.AircraftState()
1049
1050 state.timestamp = timestamp
1051
1052 state.latitude = data[self._monidx_latitude] * \
1053 90.0 / 10001750.0 / 65536.0 / 65536.0
1054
1055 state.longitude = data[self._monidx_longitude] * \
1056 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
1057 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1058
1059 state.paused = data[self._monidx_paused]!=0 or \
1060 data[self._monidx_frozen]!=0 or \
1061 data[self._monidx_replay]!=0
1062 state.trickMode = data[self._monidx_slew]!=0
1063
1064 state.overspeed = data[self._monidx_overspeed]!=0
1065 state.stalled = data[self._monidx_stalled]!=0
1066 state.onTheGround = data[self._monidx_onTheGround]!=0
1067
1068 state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
1069 state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
1070
1071 state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
1072
1073 state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
1074 state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
1075
1076 state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
1077 state.mach = data[self._monidx_mach] / 20480.0
1078 state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
1079 state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
1080
1081 state.radioAltitude = \
1082 Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
1083 state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
1084
1085 state.gLoad = data[self._monidx_gLoad] / 625.0
1086
1087 numNotchesM1 = len(self._flapsNotches) - 1
1088 flapsIncrement = 16383 / numNotchesM1
1089 flapsControl = data[self._monidx_flapsControl]
1090 flapsIndex = flapsControl / flapsIncrement
1091 if flapsIndex < numNotchesM1:
1092 if (flapsControl - (flapsIndex*flapsIncrement) >
1093 (flapsIndex+1)*flapsIncrement - flapsControl):
1094 flapsIndex += 1
1095 state.flapsSet = self._flapsNotches[flapsIndex]
1096
1097 flapsLeft = data[self._monidx_flapsLeft]
1098 state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
1099
1100 lights = data[self._monidx_lights]
1101
1102 state.navLightsOn = (lights&0x01) != 0
1103 state.antiCollisionLightsOn = (lights&0x02) != 0
1104 state.landingLightsOn = (lights&0x04) != 0
1105 state.strobeLightsOn = (lights&0x10) != 0
1106
1107 state.pitotHeatOn = data[self._monidx_pitot]!=0
1108
1109 state.parking = data[self._monidx_parking]!=0
1110
1111 state.gearsDown = data[self._monidx_noseGear]==16383
1112
1113 state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
1114
1115 spoilers = data[self._monidx_spoilers]
1116 if spoilers<=4800:
1117 state.spoilersExtension = 0.0
1118 else:
1119 state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
1120
1121 state.altimeter = data[self._monidx_altimeter] / 16.0
1122
1123 state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
1124 state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
1125 state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
1126
1127 state.windSpeed = data[self._monidx_windSpeed]
1128 state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
1129 if state.windDirection<0.0: state.windDirection += 360.0
1130
1131 state.visibility = data[self._monidx_visibility]*1609.344/100.0
1132
1133 return state
1134
1135#------------------------------------------------------------------------------
1136
1137class GenericAircraftModel(AircraftModel):
1138 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1139 values and some other common parameters in a generic way."""
1140
1141 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1142 """Construct the generic aircraft model with the given data.
1143
1144 flapsNotches is an array of how much degrees the individual flaps
1145 notches mean.
1146
1147 fuelTanks is an array of const.FUELTANK_XXX constants about the
1148 aircraft's fuel tanks. They will be converted to offsets.
1149
1150 numEngines is the number of engines the aircraft has.
1151
1152 isN1 determines if the engines have an N1 value or an RPM value
1153 (e.g. pistons)."""
1154 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1155
1156 self._fuelTanks = fuelTanks
1157 self._fuelStartIndex = None
1158 self._numEngines = numEngines
1159 self._engineStartIndex = None
1160 self._isN1 = isN1
1161
1162 def doesHandle(self, aircraft, aircraftName):
1163 """Determine if the model handles the given aircraft name.
1164
1165 This implementation returns True."""
1166 return True
1167
1168 def addMonitoringData(self, data):
1169 """Add the model-specific monitoring data to the given array."""
1170 super(GenericAircraftModel, self).addMonitoringData(data)
1171
1172 self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
1173
1174 self._fuelStartIndex = len(data)
1175 for tank in self._fuelTanks:
1176 offset = _tank2offset[tank]
1177 self._addOffsetWithIndexMember(data, offset, "u") # tank level
1178 self._addOffsetWithIndexMember(data, offset+4, "u") # tank capacity
1179
1180 if self._isN1:
1181 self._engineStartIndex = len(data)
1182 for i in range(0, self._numEngines):
1183 self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f") # N1
1184 self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h") # throttle lever
1185
1186 def getAircraftState(self, aircraft, timestamp, data):
1187 """Get the aircraft state.
1188
1189 Get it from the parent, and then add the data about the fuel levels and
1190 the engine parameters."""
1191 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1192 timestamp,
1193 data)
1194
1195 fuelWeight = data[self._monidx_fuelWeight]/256.0
1196 state.fuel = []
1197 for i in range(self._fuelStartIndex,
1198 self._fuelStartIndex + 2*len(self._fuelTanks), 2):
1199 fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
1200 state.fuel.append(fuel)
1201
1202 state.n1 = []
1203 state.reverser = []
1204 for i in range(self._engineStartIndex,
1205 self._engineStartIndex + 2*self._numEngines, 2):
1206 state.n1.append(data[i])
1207 state.reverser.append(data[i+1]<0)
1208
1209 return state
1210
1211#------------------------------------------------------------------------------
1212
1213class GenericModel(GenericAircraftModel):
1214 """Generic aircraft model for an unknown type."""
1215 def __init__(self):
1216 """Construct the model."""
1217 super(GenericModel, self). \
1218 __init__(flapsNotches = [0, 10, 20, 30],
1219 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1220 numEngines = 2)
1221
1222 @property
1223 def name(self):
1224 """Get the name for this aircraft model."""
1225 return "FSUIPC/Generic"
1226
1227#------------------------------------------------------------------------------
1228
1229class B737Model(GenericAircraftModel):
1230 """Generic model for the Boeing 737 Classing and NG aircraft."""
1231 def __init__(self):
1232 """Construct the model."""
1233 super(B737Model, self). \
1234 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1235 fuelTanks = acft.Boeing737.fuelTanks,
1236 numEngines = 2)
1237
1238 @property
1239 def name(self):
1240 """Get the name for this aircraft model."""
1241 return "FSUIPC/Generic Boeing 737"
1242
1243#------------------------------------------------------------------------------
1244
1245class PMDGBoeing737NGModel(B737Model):
1246 """A model handler for the PMDG Boeing 737NG model."""
1247 @staticmethod
1248 def doesHandle(aircraft, (name, airPath)):
1249 """Determine if this model handler handles the aircraft with the given
1250 name."""
1251 return aircraft.type in [const.AIRCRAFT_B736,
1252 const.AIRCRAFT_B737,
1253 const.AIRCRAFT_B738] and \
1254 (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
1255 (name.find("737")!=-1 or airPath.find("737")!=-1) and \
1256 (name.find("600")!=-1 or airPath.find("600")!=-1 or \
1257 name.find("700")!=-1 or airPath.find("700")!=-1 or \
1258 name.find("800")!=-1 or airPath.find("800")!=-1 or \
1259 name.find("900")!=-1 or airPath.find("900")!=-1)
1260
1261 @property
1262 def name(self):
1263 """Get the name for this aircraft model."""
1264 return "FSUIPC/PMDG Boeing 737NG"
1265
1266 def addMonitoringData(self, data):
1267 """Add the model-specific monitoring data to the given array."""
1268 super(PMDGBoeing737NGModel, self).addMonitoringData(data)
1269
1270 self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
1271
1272 def getAircraftState(self, aircraft, timestamp, data):
1273 """Get the aircraft state.
1274
1275 Get it from the parent, and then check some PMDG-specific stuff."""
1276 state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
1277 timestamp,
1278 data)
1279 if data[self._pmdgidx_switches]&0x01==0x01:
1280 state.altimeter = 1013.25
1281
1282 return state
1283
1284#------------------------------------------------------------------------------
1285
1286class B767Model(GenericAircraftModel):
1287 """Generic model for the Boeing 767 aircraft."""
1288 def __init__(self):
1289 """Construct the model."""
1290 super(B767Model, self). \
1291 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1292 fuelTanks = acft.Boeing767.fuelTanks,
1293 numEngines = 2)
1294
1295 @property
1296 def name(self):
1297 """Get the name for this aircraft model."""
1298 return "FSUIPC/Generic Boeing 767"
1299
1300#------------------------------------------------------------------------------
1301
1302class DH8DModel(GenericAircraftModel):
1303 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1304 def __init__(self):
1305 """Construct the model."""
1306 super(DH8DModel, self). \
1307 __init__(flapsNotches = [0, 5, 10, 15, 35],
1308 fuelTanks = acft.DH8D.fuelTanks,
1309 numEngines = 2)
1310
1311 @property
1312 def name(self):
1313 """Get the name for this aircraft model."""
1314 return "FSUIPC/Generic Bombardier Dash 8-Q400"
1315
1316#------------------------------------------------------------------------------
1317
1318class DreamwingsDH8DModel(DH8DModel):
1319 """Model handler for the Dreamwings Dash 8-Q400."""
1320 @staticmethod
1321 def doesHandle(aircraft, (name, airPath)):
1322 """Determine if this model handler handles the aircraft with the given
1323 name."""
1324 return aircraft.type==const.AIRCRAFT_DH8D and \
1325 (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
1326 (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
1327 (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
1328 airPath.find("Dash8Q400")!=-1
1329
1330 @property
1331 def name(self):
1332 """Get the name for this aircraft model."""
1333 return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
1334
1335 def getAircraftState(self, aircraft, timestamp, data):
1336 """Get the aircraft state.
1337
1338 Get it from the parent, and then invert the pitot heat state."""
1339 state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
1340 timestamp,
1341 data)
1342 state.pitotHeatOn = not state.pitotHeatOn
1343
1344 return state
1345#------------------------------------------------------------------------------
1346
1347class CRJ2Model(GenericAircraftModel):
1348 """Generic model for the Bombardier CRJ-200 aircraft."""
1349 def __init__(self):
1350 """Construct the model."""
1351 super(CRJ2Model, self). \
1352 __init__(flapsNotches = [0, 8, 20, 30, 45],
1353 fuelTanks = acft.CRJ2.fuelTanks,
1354 numEngines = 2)
1355
1356 @property
1357 def name(self):
1358 """Get the name for this aircraft model."""
1359 return "FSUIPC/Generic Bombardier CRJ-200"
1360
1361#------------------------------------------------------------------------------
1362
1363class F70Model(GenericAircraftModel):
1364 """Generic model for the Fokker F70 aircraft."""
1365 def __init__(self):
1366 """Construct the model."""
1367 super(F70Model, self). \
1368 __init__(flapsNotches = [0, 8, 15, 25, 42],
1369 fuelTanks = acft.F70.fuelTanks,
1370 numEngines = 2)
1371
1372 @property
1373 def name(self):
1374 """Get the name for this aircraft model."""
1375 return "FSUIPC/Generic Fokker 70"
1376
1377#------------------------------------------------------------------------------
1378
1379class DC3Model(GenericAircraftModel):
1380 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
1381 def __init__(self):
1382 """Construct the model."""
1383 super(DC3Model, self). \
1384 __init__(flapsNotches = [0, 15, 30, 45],
1385 fuelTanks = acft.DC3.fuelTanks,
1386 numEngines = 2)
1387
1388 @property
1389 def name(self):
1390 """Get the name for this aircraft model."""
1391 return "FSUIPC/Generic Lisunov Li-2"
1392
1393#------------------------------------------------------------------------------
1394
1395class T134Model(GenericAircraftModel):
1396 """Generic model for the Tupolev Tu-134 aircraft."""
1397 def __init__(self):
1398 """Construct the model."""
1399 super(T134Model, self). \
1400 __init__(flapsNotches = [0, 10, 20, 30],
1401 fuelTanks = acft.T134.fuelTanks,
1402 numEngines = 2)
1403
1404 @property
1405 def name(self):
1406 """Get the name for this aircraft model."""
1407 return "FSUIPC/Generic Tupolev Tu-134"
1408
1409#------------------------------------------------------------------------------
1410
1411class T154Model(GenericAircraftModel):
1412 """Generic model for the Tupolev Tu-134 aircraft."""
1413 def __init__(self):
1414 """Construct the model."""
1415 super(T154Model, self). \
1416 __init__(flapsNotches = [0, 15, 28, 45],
1417 fuelTanks = acft.T154.fuelTanks,
1418 numEngines = 3)
1419
1420 @property
1421 def name(self):
1422 """Get the name for this aircraft model."""
1423 return "FSUIPC/Generic Tupolev Tu-154"
1424
1425 def getAircraftState(self, aircraft, timestamp, data):
1426 """Get an aircraft state object for the given monitoring data.
1427
1428 This removes the reverser value for the middle engine."""
1429 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
1430 del state.reverser[1]
1431 return state
1432
1433#------------------------------------------------------------------------------
1434
1435class YK40Model(GenericAircraftModel):
1436 """Generic model for the Yakovlev Yak-40 aircraft."""
1437 def __init__(self):
1438 """Construct the model."""
1439 super(YK40Model, self). \
1440 __init__(flapsNotches = [0, 20, 35],
1441 fuelTanks = acft.YK40.fuelTanks,
1442 numEngines = 2)
1443
1444 @property
1445 def name(self):
1446 """Get the name for this aircraft model."""
1447 return "FSUIPC/Generic Yakovlev Yak-40"
1448
1449#------------------------------------------------------------------------------
1450
1451_genericModels = { const.AIRCRAFT_B736 : B737Model,
1452 const.AIRCRAFT_B737 : B737Model,
1453 const.AIRCRAFT_B738 : B737Model,
1454 const.AIRCRAFT_B733 : B737Model,
1455 const.AIRCRAFT_B734 : B737Model,
1456 const.AIRCRAFT_B735 : B737Model,
1457 const.AIRCRAFT_DH8D : DH8DModel,
1458 const.AIRCRAFT_B762 : B767Model,
1459 const.AIRCRAFT_B763 : B767Model,
1460 const.AIRCRAFT_CRJ2 : B767Model,
1461 const.AIRCRAFT_F70 : F70Model,
1462 const.AIRCRAFT_DC3 : DC3Model,
1463 const.AIRCRAFT_T134 : T134Model,
1464 const.AIRCRAFT_T154 : T154Model,
1465 const.AIRCRAFT_YK40 : YK40Model }
1466
1467#------------------------------------------------------------------------------
1468
1469AircraftModel.registerSpecial(PMDGBoeing737NGModel)
1470AircraftModel.registerSpecial(DreamwingsDH8DModel)
1471
1472#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.