source: src/mlx/fsuipc.py@ 166:e4ba22b7a13b

Last change on this file since 166:e4ba22b7a13b was 162:b565d30d01b3, checked in by István Váradi <ivaradi@…>, 12 years ago

Added support for forcing the Windows version to the PyUIPC simulator

File size: 56.1 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" and "FORCE_PYUIPC_SIM" not in os.environ:
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 if self._monitoringRequested and not self._monitoring:
715 self._stopNormal()
716 self._startMonitoring()
717 elif self._monitoring and not self._monitoringRequested:
718 self._stopNormal()
719 self._startDefaultNormal()
720 elif self._monitoring and self._aircraftModel is not None and \
721 not createdNewModel:
722 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
723 timestamp, data)
724
725 self._checkTimeSync(aircraftState)
726
727 self._aircraft.handleState(aircraftState)
728
729 def _checkTimeSync(self, aircraftState):
730 """Check if we need to synchronize the FS time."""
731 if not self._syncTime or aircraftState.paused or \
732 self._flareRequestID is not None:
733 self._nextSyncTime = -1
734 return
735
736 now = time.time()
737 seconds = time.gmtime(now).tm_sec
738
739 if seconds>30 and seconds<59:
740 if self._nextSyncTime > (now - 0.49):
741 return
742
743 self._handler.requestWrite([(0x023a, "b", int(seconds))],
744 self._handleTimeSynced)
745
746 #print "Set the seconds to ", seconds
747
748 if self._nextSyncTime<0:
749 self._nextSyncTime = now
750
751 self._nextSyncTime += Simulator.TIME_SYNC_INTERVAL
752 else:
753 self._nextSyncTime = -1
754
755 def _handleTimeSynced(self, success, extra):
756 """Callback for the time sync result."""
757 pass
758
759 def _setAircraftName(self, timestamp, name, airPath):
760 """Set the name of the aicraft and if it is different from the
761 previous, create a new model for it.
762
763 If so, also notifty the aircraft about the change.
764
765 Return if a new model was created."""
766 aircraftName = (name, airPath)
767 if aircraftName==self._aircraftName:
768 return False
769
770 self._aircraftName = aircraftName
771 needNew = self._aircraftModel is None
772 needNew = needNew or\
773 not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
774 if not needNew:
775 specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
776 needNew = specialModel is not None and \
777 specialModel is not self._aircraftModel.__class__
778
779 if needNew:
780 self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
781
782
783 self._aircraft.modelChanged(timestamp, self._latin1decoder(name)[0],
784 self._aircraftModel.name)
785
786 return needNew
787
788 def _setAircraftModel(self, model):
789 """Set a new aircraft model.
790
791 It will be queried for the data to monitor and the monitoring request
792 will be replaced by a new one."""
793 self._aircraftModel = model
794
795 if self._monitoring:
796 self._stopNormal()
797 self._startMonitoring()
798
799 def _startMonitoring(self):
800 """Start monitoring with the current aircraft model."""
801 data = Simulator.normalData[:]
802 self._aircraftModel.addMonitoringData(data)
803
804 self._normalRequestID = \
805 self._handler.requestPeriodicRead(1.0, data,
806 self._handleNormal,
807 validator = self._validateNormal)
808 self._monitoring = True
809
810 def _addFlareRate(self, data):
811 """Append a flare rate to the list of last rates."""
812 if len(self._flareRates)>=3:
813 del self._flareRates[0]
814 self._flareRates.append(Handler.fsuipc2VS(data))
815
816 def _handleFlare1(self, data, normal):
817 """Handle the first stage of flare monitoring."""
818 #self._aircraft.logger.debug("handleFlare1: " + str(data))
819 if Handler.fsuipc2radioAltitude(data[1])<=50.0:
820 self._flareStart = time.time()
821 self._flareStartFS = data[0]
822 self._handler.clearPeriodic(self._flareRequestID)
823 self._flareRequestID = \
824 self._handler.requestPeriodicRead(0.1,
825 Simulator.flareData2,
826 self._handleFlare2)
827 self._handler.requestRead(Simulator.flareStartData,
828 self._handleFlareStart)
829
830 self._addFlareRate(data[2])
831
832 def _handleFlareStart(self, data, extra):
833 """Handle the data need to notify the aircraft about the starting of
834 the flare."""
835 #self._aircraft.logger.debug("handleFlareStart: " + str(data))
836 if data is not None:
837 windDirection = data[1]*360.0/65536.0
838 if windDirection<0.0: windDirection += 360.0
839 self._aircraft.flareStarted(data[0], windDirection,
840 data[2]*1609.344/100.0,
841 self._flareStart, self._flareStartFS)
842
843 def _handleFlare2(self, data, normal):
844 """Handle the first stage of flare monitoring."""
845 #self._aircraft.logger.debug("handleFlare2: " + str(data))
846 if data[1]!=0:
847 flareEnd = time.time()
848 self._handler.clearPeriodic(self._flareRequestID)
849 self._flareRequestID = None
850
851 flareEndFS = data[0]
852 if flareEndFS<self._flareStartFS:
853 flareEndFS += 60
854
855 tdRate = Handler.fsuipc2VS(data[3])
856 tdRateCalculatedByFS = True
857 if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
858 tdRate = min(self._flareRates)
859 tdRateCalculatedByFS = False
860
861 self._aircraft.flareFinished(flareEnd, flareEndFS,
862 tdRate, tdRateCalculatedByFS,
863 Handler.fsuipc2IAS(data[4]),
864 Handler.fsuipc2Degrees(data[5]),
865 Handler.fsuipc2Degrees(data[6]),
866 Handler.fsuipc2PositiveDegrees(data[7]))
867 else:
868 self._addFlareRate(data[2])
869
870 def _handleZFW(self, data, callback):
871 """Callback for a ZFW retrieval request."""
872 zfw = data[0] * const.LBSTOKG / 256.0
873 callback(zfw)
874
875 def _handleTime(self, data, callback):
876 """Callback for a time retrieval request."""
877 callback(Simulator._getTimestamp(data))
878
879 def _handlePayloadCount(self, data, callback):
880 """Callback for the payload count retrieval request."""
881 payloadCount = data[0]
882 data = [(0x3bfc, "d"), (0x30c0, "f")]
883 for i in range(0, payloadCount):
884 data.append((0x1400 + i*48, "f"))
885
886 self._handler.requestRead(data, self._handleWeights,
887 extra = callback)
888
889 def _handleWeights(self, data, callback):
890 """Callback for the weights retrieval request."""
891 zfw = data[0] * const.LBSTOKG / 256.0
892 grossWeight = data[1] * const.LBSTOKG
893 payload = sum(data[2:]) * const.LBSTOKG
894 dow = zfw - payload
895 callback(dow, payload, zfw, grossWeight)
896
897 def _handleMessageSent(self, success, disconnect):
898 """Callback for a message sending request."""
899 #print "fsuipc.Simulator._handleMessageSent", disconnect
900 if disconnect:
901 self._handler.disconnect()
902
903 def _handleFuelRetrieved(self, data, callback):
904 """Callback for a fuel retrieval request."""
905 fuelWeight = data[0] / 256.0
906 result = []
907 for i in range(1, len(data), 2):
908 capacity = data[i+1] * fuelWeight * const.LBSTOKG
909 amount = data[i] * capacity / 128.0 / 65536.0
910 result.append( (amount, capacity) )
911
912 callback(result)
913
914 def _handleFuelWritten(self, success, extra):
915 """Callback for a fuel setting request."""
916 pass
917
918#------------------------------------------------------------------------------
919
920class AircraftModel(object):
921 """Base class for the aircraft models.
922
923 Aircraft models handle the data arriving from FSUIPC and turn it into an
924 object describing the aircraft's state."""
925 monitoringData = [("paused", 0x0264, "H"),
926 ("latitude", 0x0560, "l"),
927 ("longitude", 0x0568, "l"),
928 ("frozen", 0x3364, "H"),
929 ("replay", 0x0628, "d"),
930 ("slew", 0x05dc, "H"),
931 ("overspeed", 0x036d, "b"),
932 ("stalled", 0x036c, "b"),
933 ("onTheGround", 0x0366, "H"),
934 ("zfw", 0x3bfc, "d"),
935 ("grossWeight", 0x30c0, "f"),
936 ("heading", 0x0580, "d"),
937 ("pitch", 0x0578, "d"),
938 ("bank", 0x057c, "d"),
939 ("ias", 0x02bc, "d"),
940 ("mach", 0x11c6, "H"),
941 ("groundSpeed", 0x02b4, "d"),
942 ("vs", 0x02c8, "d"),
943 ("radioAltitude", 0x31e4, "d"),
944 ("altitude", 0x0570, "l"),
945 ("gLoad", 0x11ba, "H"),
946 ("flapsControl", 0x0bdc, "d"),
947 ("flapsLeft", 0x0be0, "d"),
948 ("flapsRight", 0x0be4, "d"),
949 ("lights", 0x0d0c, "H"),
950 ("pitot", 0x029c, "b"),
951 ("parking", 0x0bc8, "H"),
952 ("noseGear", 0x0bec, "d"),
953 ("spoilersArmed", 0x0bcc, "d"),
954 ("spoilers", 0x0bd0, "d"),
955 ("altimeter", 0x0330, "H"),
956 ("nav1", 0x0350, "H"),
957 ("nav2", 0x0352, "H"),
958 ("squawk", 0x0354, "H"),
959 ("windSpeed", 0x0e90, "H"),
960 ("windDirection", 0x0e92, "H"),
961 ("visibility", 0x0e8a, "H")]
962
963
964 specialModels = []
965
966 @staticmethod
967 def registerSpecial(clazz):
968 """Register the given class as a special model."""
969 AircraftModel.specialModels.append(clazz)
970
971 @staticmethod
972 def findSpecial(aircraft, aircraftName):
973 for specialModel in AircraftModel.specialModels:
974 if specialModel.doesHandle(aircraft, aircraftName):
975 return specialModel
976 return None
977
978 @staticmethod
979 def create(aircraft, aircraftName):
980 """Create the model for the given aircraft name, and notify the
981 aircraft about it."""
982 specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
983 if specialModel is not None:
984 return specialModel()
985 if aircraft.type in _genericModels:
986 return _genericModels[aircraft.type]()
987 else:
988 return GenericModel()
989
990 @staticmethod
991 def convertBCD(data, length):
992 """Convert a data item encoded as BCD into a string of the given number
993 of digits."""
994 bcd = ""
995 for i in range(0, length):
996 digit = chr(ord('0') + (data&0x0f))
997 data >>= 4
998 bcd = digit + bcd
999 return bcd
1000
1001 @staticmethod
1002 def convertFrequency(data):
1003 """Convert the given frequency data to a string."""
1004 bcd = AircraftModel.convertBCD(data, 4)
1005 return "1" + bcd[0:2] + "." + bcd[2:4]
1006
1007 def __init__(self, flapsNotches):
1008 """Construct the aircraft model.
1009
1010 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
1011 self._flapsNotches = flapsNotches
1012
1013 @property
1014 def name(self):
1015 """Get the name for this aircraft model."""
1016 return "FSUIPC/Generic"
1017
1018 def doesHandle(self, aircraft, aircraftName):
1019 """Determine if the model handles the given aircraft name.
1020
1021 This default implementation returns False."""
1022 return False
1023
1024 def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
1025 """Add the given FSUIPC offset and type to the given array and a member
1026 attribute with the given name."""
1027 dest.append((offset, type))
1028 if attrName is not None:
1029 setattr(self, attrName, len(dest)-1)
1030
1031 def _addDataWithIndexMembers(self, dest, prefix, data):
1032 """Add FSUIPC data to the given array and also corresponding index
1033 member variables with the given prefix.
1034
1035 data is a list of triplets of the following items:
1036 - the name of the data item. The index member variable will have a name
1037 created by prepending the given prefix to this name.
1038 - the FSUIPC offset
1039 - the FSUIPC type
1040
1041 The latter two items will be appended to dest."""
1042 for (name, offset, type) in data:
1043 self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
1044
1045 def addMonitoringData(self, data):
1046 """Add the model-specific monitoring data to the given array."""
1047 self._addDataWithIndexMembers(data, "_monidx_",
1048 AircraftModel.monitoringData)
1049
1050 def getAircraftState(self, aircraft, timestamp, data):
1051 """Get an aircraft state object for the given monitoring data."""
1052 state = fs.AircraftState()
1053
1054 state.timestamp = timestamp
1055
1056 state.latitude = data[self._monidx_latitude] * \
1057 90.0 / 10001750.0 / 65536.0 / 65536.0
1058
1059 state.longitude = data[self._monidx_longitude] * \
1060 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
1061 if state.longitude>180.0: state.longitude = 360.0 - state.longitude
1062
1063 state.paused = data[self._monidx_paused]!=0 or \
1064 data[self._monidx_frozen]!=0 or \
1065 data[self._monidx_replay]!=0
1066 state.trickMode = data[self._monidx_slew]!=0
1067
1068 state.overspeed = data[self._monidx_overspeed]!=0
1069 state.stalled = data[self._monidx_stalled]!=0
1070 state.onTheGround = data[self._monidx_onTheGround]!=0
1071
1072 state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
1073 state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
1074
1075 state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
1076
1077 state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
1078 state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
1079
1080 state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
1081 state.mach = data[self._monidx_mach] / 20480.0
1082 state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
1083 state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
1084
1085 state.radioAltitude = \
1086 Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
1087 state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
1088
1089 state.gLoad = data[self._monidx_gLoad] / 625.0
1090
1091 numNotchesM1 = len(self._flapsNotches) - 1
1092 flapsIncrement = 16383 / numNotchesM1
1093 flapsControl = data[self._monidx_flapsControl]
1094 flapsIndex = flapsControl / flapsIncrement
1095 if flapsIndex < numNotchesM1:
1096 if (flapsControl - (flapsIndex*flapsIncrement) >
1097 (flapsIndex+1)*flapsIncrement - flapsControl):
1098 flapsIndex += 1
1099 state.flapsSet = self._flapsNotches[flapsIndex]
1100
1101 flapsLeft = data[self._monidx_flapsLeft]
1102 state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
1103
1104 lights = data[self._monidx_lights]
1105
1106 state.navLightsOn = (lights&0x01) != 0
1107 state.antiCollisionLightsOn = (lights&0x02) != 0
1108 state.landingLightsOn = (lights&0x04) != 0
1109 state.strobeLightsOn = (lights&0x10) != 0
1110
1111 state.pitotHeatOn = data[self._monidx_pitot]!=0
1112
1113 state.parking = data[self._monidx_parking]!=0
1114
1115 state.gearsDown = data[self._monidx_noseGear]==16383
1116
1117 state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
1118
1119 spoilers = data[self._monidx_spoilers]
1120 if spoilers<=4800:
1121 state.spoilersExtension = 0.0
1122 else:
1123 state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
1124
1125 state.altimeter = data[self._monidx_altimeter] / 16.0
1126
1127 state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
1128 state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
1129 state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
1130
1131 state.windSpeed = data[self._monidx_windSpeed]
1132 state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
1133 if state.windDirection<0.0: state.windDirection += 360.0
1134
1135 state.visibility = data[self._monidx_visibility]*1609.344/100.0
1136
1137 return state
1138
1139#------------------------------------------------------------------------------
1140
1141class GenericAircraftModel(AircraftModel):
1142 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
1143 values and some other common parameters in a generic way."""
1144
1145 def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
1146 """Construct the generic aircraft model with the given data.
1147
1148 flapsNotches is an array of how much degrees the individual flaps
1149 notches mean.
1150
1151 fuelTanks is an array of const.FUELTANK_XXX constants about the
1152 aircraft's fuel tanks. They will be converted to offsets.
1153
1154 numEngines is the number of engines the aircraft has.
1155
1156 isN1 determines if the engines have an N1 value or an RPM value
1157 (e.g. pistons)."""
1158 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
1159
1160 self._fuelTanks = fuelTanks
1161 self._fuelStartIndex = None
1162 self._numEngines = numEngines
1163 self._engineStartIndex = None
1164 self._isN1 = isN1
1165
1166 def doesHandle(self, aircraft, aircraftName):
1167 """Determine if the model handles the given aircraft name.
1168
1169 This implementation returns True."""
1170 return True
1171
1172 def addMonitoringData(self, data):
1173 """Add the model-specific monitoring data to the given array."""
1174 super(GenericAircraftModel, self).addMonitoringData(data)
1175
1176 self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
1177
1178 self._fuelStartIndex = len(data)
1179 for tank in self._fuelTanks:
1180 offset = _tank2offset[tank]
1181 self._addOffsetWithIndexMember(data, offset, "u") # tank level
1182 self._addOffsetWithIndexMember(data, offset+4, "u") # tank capacity
1183
1184 if self._isN1:
1185 self._engineStartIndex = len(data)
1186 for i in range(0, self._numEngines):
1187 self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f") # N1
1188 self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h") # throttle lever
1189
1190 def getAircraftState(self, aircraft, timestamp, data):
1191 """Get the aircraft state.
1192
1193 Get it from the parent, and then add the data about the fuel levels and
1194 the engine parameters."""
1195 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
1196 timestamp,
1197 data)
1198
1199 fuelWeight = data[self._monidx_fuelWeight]/256.0
1200 state.fuel = []
1201 for i in range(self._fuelStartIndex,
1202 self._fuelStartIndex + 2*len(self._fuelTanks), 2):
1203 fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
1204 state.fuel.append(fuel)
1205
1206 state.n1 = []
1207 state.reverser = []
1208 for i in range(self._engineStartIndex,
1209 self._engineStartIndex + 2*self._numEngines, 2):
1210 state.n1.append(data[i])
1211 state.reverser.append(data[i+1]<0)
1212
1213 return state
1214
1215#------------------------------------------------------------------------------
1216
1217class GenericModel(GenericAircraftModel):
1218 """Generic aircraft model for an unknown type."""
1219 def __init__(self):
1220 """Construct the model."""
1221 super(GenericModel, self). \
1222 __init__(flapsNotches = [0, 10, 20, 30],
1223 fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
1224 numEngines = 2)
1225
1226 @property
1227 def name(self):
1228 """Get the name for this aircraft model."""
1229 return "FSUIPC/Generic"
1230
1231#------------------------------------------------------------------------------
1232
1233class B737Model(GenericAircraftModel):
1234 """Generic model for the Boeing 737 Classing and NG aircraft."""
1235 def __init__(self):
1236 """Construct the model."""
1237 super(B737Model, self). \
1238 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
1239 fuelTanks = acft.Boeing737.fuelTanks,
1240 numEngines = 2)
1241
1242 @property
1243 def name(self):
1244 """Get the name for this aircraft model."""
1245 return "FSUIPC/Generic Boeing 737"
1246
1247#------------------------------------------------------------------------------
1248
1249class PMDGBoeing737NGModel(B737Model):
1250 """A model handler for the PMDG Boeing 737NG model."""
1251 @staticmethod
1252 def doesHandle(aircraft, (name, airPath)):
1253 """Determine if this model handler handles the aircraft with the given
1254 name."""
1255 return aircraft.type in [const.AIRCRAFT_B736,
1256 const.AIRCRAFT_B737,
1257 const.AIRCRAFT_B738] and \
1258 (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
1259 (name.find("737")!=-1 or airPath.find("737")!=-1) and \
1260 (name.find("600")!=-1 or airPath.find("600")!=-1 or \
1261 name.find("700")!=-1 or airPath.find("700")!=-1 or \
1262 name.find("800")!=-1 or airPath.find("800")!=-1 or \
1263 name.find("900")!=-1 or airPath.find("900")!=-1)
1264
1265 @property
1266 def name(self):
1267 """Get the name for this aircraft model."""
1268 return "FSUIPC/PMDG Boeing 737NG"
1269
1270 def addMonitoringData(self, data):
1271 """Add the model-specific monitoring data to the given array."""
1272 super(PMDGBoeing737NGModel, self).addMonitoringData(data)
1273
1274 self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
1275
1276 def getAircraftState(self, aircraft, timestamp, data):
1277 """Get the aircraft state.
1278
1279 Get it from the parent, and then check some PMDG-specific stuff."""
1280 state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
1281 timestamp,
1282 data)
1283 if data[self._pmdgidx_switches]&0x01==0x01:
1284 state.altimeter = 1013.25
1285
1286 return state
1287
1288#------------------------------------------------------------------------------
1289
1290class B767Model(GenericAircraftModel):
1291 """Generic model for the Boeing 767 aircraft."""
1292 def __init__(self):
1293 """Construct the model."""
1294 super(B767Model, self). \
1295 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
1296 fuelTanks = acft.Boeing767.fuelTanks,
1297 numEngines = 2)
1298
1299 @property
1300 def name(self):
1301 """Get the name for this aircraft model."""
1302 return "FSUIPC/Generic Boeing 767"
1303
1304#------------------------------------------------------------------------------
1305
1306class DH8DModel(GenericAircraftModel):
1307 """Generic model for the Bombardier Dash 8-Q400 aircraft."""
1308 def __init__(self):
1309 """Construct the model."""
1310 super(DH8DModel, self). \
1311 __init__(flapsNotches = [0, 5, 10, 15, 35],
1312 fuelTanks = acft.DH8D.fuelTanks,
1313 numEngines = 2)
1314
1315 @property
1316 def name(self):
1317 """Get the name for this aircraft model."""
1318 return "FSUIPC/Generic Bombardier Dash 8-Q400"
1319
1320#------------------------------------------------------------------------------
1321
1322class DreamwingsDH8DModel(DH8DModel):
1323 """Model handler for the Dreamwings Dash 8-Q400."""
1324 @staticmethod
1325 def doesHandle(aircraft, (name, airPath)):
1326 """Determine if this model handler handles the aircraft with the given
1327 name."""
1328 return aircraft.type==const.AIRCRAFT_DH8D and \
1329 (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
1330 (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
1331 (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
1332 airPath.find("Dash8Q400")!=-1
1333
1334 @property
1335 def name(self):
1336 """Get the name for this aircraft model."""
1337 return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
1338
1339 def getAircraftState(self, aircraft, timestamp, data):
1340 """Get the aircraft state.
1341
1342 Get it from the parent, and then invert the pitot heat state."""
1343 state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
1344 timestamp,
1345 data)
1346 state.pitotHeatOn = not state.pitotHeatOn
1347
1348 return state
1349#------------------------------------------------------------------------------
1350
1351class CRJ2Model(GenericAircraftModel):
1352 """Generic model for the Bombardier CRJ-200 aircraft."""
1353 def __init__(self):
1354 """Construct the model."""
1355 super(CRJ2Model, self). \
1356 __init__(flapsNotches = [0, 8, 20, 30, 45],
1357 fuelTanks = acft.CRJ2.fuelTanks,
1358 numEngines = 2)
1359
1360 @property
1361 def name(self):
1362 """Get the name for this aircraft model."""
1363 return "FSUIPC/Generic Bombardier CRJ-200"
1364
1365#------------------------------------------------------------------------------
1366
1367class F70Model(GenericAircraftModel):
1368 """Generic model for the Fokker F70 aircraft."""
1369 def __init__(self):
1370 """Construct the model."""
1371 super(F70Model, self). \
1372 __init__(flapsNotches = [0, 8, 15, 25, 42],
1373 fuelTanks = acft.F70.fuelTanks,
1374 numEngines = 2)
1375
1376 @property
1377 def name(self):
1378 """Get the name for this aircraft model."""
1379 return "FSUIPC/Generic Fokker 70"
1380
1381#------------------------------------------------------------------------------
1382
1383class DC3Model(GenericAircraftModel):
1384 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
1385 def __init__(self):
1386 """Construct the model."""
1387 super(DC3Model, self). \
1388 __init__(flapsNotches = [0, 15, 30, 45],
1389 fuelTanks = acft.DC3.fuelTanks,
1390 numEngines = 2)
1391
1392 @property
1393 def name(self):
1394 """Get the name for this aircraft model."""
1395 return "FSUIPC/Generic Lisunov Li-2"
1396
1397#------------------------------------------------------------------------------
1398
1399class T134Model(GenericAircraftModel):
1400 """Generic model for the Tupolev Tu-134 aircraft."""
1401 def __init__(self):
1402 """Construct the model."""
1403 super(T134Model, self). \
1404 __init__(flapsNotches = [0, 10, 20, 30],
1405 fuelTanks = acft.T134.fuelTanks,
1406 numEngines = 2)
1407
1408 @property
1409 def name(self):
1410 """Get the name for this aircraft model."""
1411 return "FSUIPC/Generic Tupolev Tu-134"
1412
1413#------------------------------------------------------------------------------
1414
1415class T154Model(GenericAircraftModel):
1416 """Generic model for the Tupolev Tu-134 aircraft."""
1417 def __init__(self):
1418 """Construct the model."""
1419 super(T154Model, self). \
1420 __init__(flapsNotches = [0, 15, 28, 45],
1421 fuelTanks = acft.T154.fuelTanks,
1422 numEngines = 3)
1423
1424 @property
1425 def name(self):
1426 """Get the name for this aircraft model."""
1427 return "FSUIPC/Generic Tupolev Tu-154"
1428
1429 def getAircraftState(self, aircraft, timestamp, data):
1430 """Get an aircraft state object for the given monitoring data.
1431
1432 This removes the reverser value for the middle engine."""
1433 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
1434 del state.reverser[1]
1435 return state
1436
1437#------------------------------------------------------------------------------
1438
1439class YK40Model(GenericAircraftModel):
1440 """Generic model for the Yakovlev Yak-40 aircraft."""
1441 def __init__(self):
1442 """Construct the model."""
1443 super(YK40Model, self). \
1444 __init__(flapsNotches = [0, 20, 35],
1445 fuelTanks = acft.YK40.fuelTanks,
1446 numEngines = 2)
1447
1448 @property
1449 def name(self):
1450 """Get the name for this aircraft model."""
1451 return "FSUIPC/Generic Yakovlev Yak-40"
1452
1453#------------------------------------------------------------------------------
1454
1455_genericModels = { const.AIRCRAFT_B736 : B737Model,
1456 const.AIRCRAFT_B737 : B737Model,
1457 const.AIRCRAFT_B738 : B737Model,
1458 const.AIRCRAFT_B733 : B737Model,
1459 const.AIRCRAFT_B734 : B737Model,
1460 const.AIRCRAFT_B735 : B737Model,
1461 const.AIRCRAFT_DH8D : DH8DModel,
1462 const.AIRCRAFT_B762 : B767Model,
1463 const.AIRCRAFT_B763 : B767Model,
1464 const.AIRCRAFT_CRJ2 : B767Model,
1465 const.AIRCRAFT_F70 : F70Model,
1466 const.AIRCRAFT_DC3 : DC3Model,
1467 const.AIRCRAFT_T134 : T134Model,
1468 const.AIRCRAFT_T154 : T154Model,
1469 const.AIRCRAFT_YK40 : YK40Model }
1470
1471#------------------------------------------------------------------------------
1472
1473AircraftModel.registerSpecial(PMDGBoeing737NGModel)
1474AircraftModel.registerSpecial(DreamwingsDH8DModel)
1475
1476#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.