source: src/fsuipc.py@ 8:85698811c70e

Last change on this file since 8:85698811c70e was 8:85698811c70e, checked in by István Váradi <ivaradi@…>, 12 years ago

The tracking of flight stages and some basic logging functionality works

File size: 37.0 KB
Line 
1# Module handling the connection to FSUIPC
2
3#------------------------------------------------------------------------------
4
5import fs
6import const
7import util
8
9import threading
10import os
11import time
12import calendar
13import sys
14
15if os.name == "nt":
16 import pyuipc
17else:
18 import pyuipc_emu as pyuipc
19
20#------------------------------------------------------------------------------
21
22class Handler(threading.Thread):
23 """The thread to handle the FSUIPC requests."""
24 @staticmethod
25 def _callSafe(fun):
26 """Call the given function and swallow any exceptions."""
27 try:
28 return fun()
29 except Exception, e:
30 print >> sys.stderr, str(e)
31 return None
32
33 class Request(object):
34 """A simple, one-shot request."""
35 def __init__(self, forWrite, data, callback, extra):
36 """Construct the request."""
37 self._forWrite = forWrite
38 self._data = data
39 self._callback = callback
40 self._extra = extra
41
42 def process(self, time):
43 """Process the request."""
44 if self._forWrite:
45 pyuipc.write(self._data)
46 Handler._callSafe(lambda: self._callback(True, self._extra))
47 else:
48 values = pyuipc.read(self._data)
49 Handler._callSafe(lambda: self._callback(values, self._extra))
50
51 return True
52
53 def fail(self):
54 """Handle the failure of this request."""
55 if self._forWrite:
56 Handler._callSafe(lambda: self._callback(False, self._extra))
57 else:
58 Handler._callSafe(lambda: self._callback(None, self._extra))
59
60 class PeriodicRequest(object):
61 """A periodic request."""
62 def __init__(self, id, period, data, callback, extra):
63 """Construct the periodic request."""
64 self._id = id
65 self._period = period
66 self._nextFire = time.time() + period
67 self._data = data
68 self._preparedData = None
69 self._callback = callback
70 self._extra = extra
71
72 @property
73 def id(self):
74 """Get the ID of this periodic request."""
75 return self._id
76
77 @property
78 def nextFire(self):
79 """Get the next firing time."""
80 return self._nextFire
81
82 def process(self, time):
83 """Check if this request should be executed, and if so, do so.
84
85 Return a boolean indicating if the request was executed."""
86 if time < self._nextFire:
87 return False
88
89 if self._preparedData is None:
90 self._preparedData = pyuipc.prepare_data(self._data)
91 self._data = None
92
93 values = pyuipc.read(self._preparedData)
94
95 Handler._callSafe(lambda: self._callback(values, self._extra))
96
97 while self._nextFire <= time:
98 self._nextFire += self._period
99
100 return True
101
102 def fail(self):
103 """Handle the failure of this request."""
104 pass
105
106 def __cmp__(self, other):
107 """Compare two periodic requests. They are ordered by their next
108 firing times."""
109 return cmp(self._nextFire, other._nextFire)
110
111 def __init__(self, connectionListener):
112 """Construct the handler with the given connection listener."""
113 threading.Thread.__init__(self)
114
115 self._connectionListener = connectionListener
116
117 self._requestCondition = threading.Condition()
118 self._connectionRequested = False
119
120 self._requests = []
121 self._nextPeriodicID = 1
122 self._periodicRequests = []
123
124 self.daemon = True
125
126 def requestRead(self, data, callback, extra = None):
127 """Request the reading of some data.
128
129 data is a list of tuples of the following items:
130 - the offset of the data as an integer
131 - the type letter of the data as a string
132
133 callback is a function that receives two pieces of data:
134 - the values retrieved or None on error
135 - the extra parameter
136
137 It will be called in the handler's thread!
138 """
139 with self._requestCondition:
140 self._requests.append(Handler.Request(False, data, callback, extra))
141 self._requestCondition.notify()
142
143 def requestWrite(self, data, callback, extra = None):
144 """Request the writing of some data.
145
146 data is a list of tuples of the following items:
147 - the offset of the data as an integer
148 - the type letter of the data as a string
149 - the data to write
150
151 callback is a function that receives two pieces of data:
152 - a boolean indicating if writing was successful
153 - the extra data
154 It will be called in the handler's thread!
155 """
156 with self._requestCondition:
157 self._requests.append(Handler.Request(True, data, callback, extra))
158 self._requestCondition.notify()
159
160 @staticmethod
161 def _readWriteCallback(data, extra):
162 """Callback for the read() and write() calls below."""
163 extra.append(data)
164 with extra[0] as condition:
165 condition.notify()
166
167 def read(self, data):
168 """Read the given data synchronously.
169
170 If a problem occurs, an exception is thrown."""
171 with threading.Condition() as condition:
172 extra = [condition]
173 self._requestRead(data, self._readWriteCallback, extra)
174 while len(extra)<2:
175 condition.wait()
176 if extra[1] is None:
177 raise fs.SimulatorException("reading failed")
178 else:
179 return extra[1]
180
181 def write(self, data):
182 """Write the given data synchronously.
183
184 If a problem occurs, an exception is thrown."""
185 with threading.Condition() as condition:
186 extra = [condition]
187 self._requestWrite(data, self._writeCallback, extra)
188 while len(extra)<2:
189 condition.wait()
190 if extra[1] is None:
191 raise fs.SimulatorException("writing failed")
192
193 def requestPeriodicRead(self, period, data, callback, extra = None):
194 """Request a periodic read of data.
195
196 period is a floating point number with the period in seconds.
197
198 This function returns an identifier which can be used to cancel the
199 request."""
200 with self._requestCondition:
201 id = self._nextPeriodicID
202 self._nextPeriodicID += 1
203 request = Handler.PeriodicRequest(id, period, data, callback, extra)
204 self._periodicRequests.append(request)
205 self._requestCondition.notify()
206 return id
207
208 def clearPeriodic(self, id):
209 """Clear the periodic request with the given ID."""
210 with self._requestCondition:
211 for i in range(0, len(self._periodicRequests)):
212 if self._periodicRequests[i].id==id:
213 del self._periodicRequests[i]
214 return True
215 return False
216
217 def connect(self):
218 """Initiate the connection to the flight simulator."""
219 with self._requestCondition:
220 if not self._connectionRequested:
221 self._connectionRequested = True
222 self._requestCondition.notify()
223
224 def disconnect(self):
225 """Disconnect from the flight simulator."""
226 with self._requestCondition:
227 if self._connectionRequested:
228 self._connectionRequested = False
229 self._requestCondition.notify()
230
231 def run(self):
232 """Perform the operation of the thread."""
233 while True:
234 self._waitConnectionRequest()
235
236 if self._connect():
237 self._handleConnection()
238
239 self._disconnect()
240
241 def _waitConnectionRequest(self):
242 """Wait for a connection request to arrive."""
243 with self._requestCondition:
244 while not self._connectionRequested:
245 self._requestCondition.wait()
246
247 def _connect(self):
248 """Try to connect to the flight simulator via FSUIPC"""
249 while self._connectionRequested:
250 try:
251 pyuipc.open(pyuipc.SIM_FS2K4)
252 description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
253 (pyuipc.fsuipc_version, pyuipc.lib_version,
254 pyuipc.fs_version)
255 Handler._callSafe(lambda:
256 self._connectionListener.connected(const.SIM_MSFS9,
257 description))
258 return True
259 except Exception, e:
260 print "fsuipc.Handler._connect: connection failed: " + str(e)
261 time.sleep(0.1)
262
263 return False
264
265 def _handleConnection(self):
266 """Handle a living connection."""
267 with self._requestCondition:
268 while self._connectionRequested:
269 if not self._processRequests():
270 return
271 timeout = None
272 if self._periodicRequests:
273 self._periodicRequests.sort()
274 timeout = self._periodicRequests[0].nextFire - time.time()
275 if timeout is None or timeout > 0.0:
276 self._requestCondition.wait(timeout)
277
278 def _disconnect(self):
279 """Disconnect from the flight simulator."""
280 pyuipc.close()
281 Handler._callSafe(lambda: self._connectionListener.disconnected())
282
283 def _failRequests(self, request):
284 """Fail the outstanding, single-shot requuests."""
285 request.fail()
286 with self._requestCondition:
287 for request in self._requests:
288 try:
289 self._requestCondition.release()
290 request.fail()
291 finally:
292 self._requestCondition.acquire()
293 self._requests = []
294
295 def _processRequest(self, request, time):
296 """Process the given request.
297
298 If an exception occurs, we try to reconnect.
299
300 Returns what the request's process() function returned or None if
301 reconnection failed."""
302
303 self._requestCondition.release()
304
305 try:
306 return request.process(time)
307 except Exception as e:
308 print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
309 str(e) + ") reconnecting."
310 self._disconnect()
311 self._failRequests(request)
312 if not self._connect(): return None
313 else: return True
314 finally:
315 self._requestCondition.acquire()
316
317 def _processRequests(self):
318 """Process any pending requests.
319
320 Will be called with the request lock held."""
321 while self._connectionRequested and self._periodicRequests:
322 self._periodicRequests.sort()
323 request = self._periodicRequests[0]
324 result = self._processRequest(request, time.time())
325 if result is None: return False
326 elif not result: break
327
328 while self._connectionRequested and self._requests:
329 request = self._requests[0]
330 del self._requests[0]
331
332 if self._processRequest(request, None) is None:
333 return False
334
335 return self._connectionRequested
336
337#------------------------------------------------------------------------------
338
339class Simulator(object):
340 """The simulator class representing the interface to the flight simulator
341 via FSUIPC."""
342 # The basic data that should be queried all the time once we are connected
343 normalData = [ (0x0240, "H"),
344 (0x023e, "H"),
345 (0x023b, "b"),
346 (0x023c, "b"),
347 (0x023a, "b"),
348 (0x3d00, -256),
349 (0x3c00, -256) ]
350
351 def __init__(self, connectionListener, aircraft):
352 """Construct the simulator.
353
354 The aircraft object passed must provide the following members:
355 - type: one of the AIRCRAFT_XXX constants from const.py
356 - modelChanged(aircraftName, modelName): called when the model handling
357 the aircraft has changed.
358 - handleState(aircraftState): handle the given state."""
359 self._aircraft = aircraft
360
361 self._handler = Handler(connectionListener)
362 self._handler.start()
363
364 self._normalRequestID = None
365
366 self._monitoringRequested = False
367 self._monitoring = False
368
369 self._aircraftName = None
370 self._aircraftModel = None
371
372 def connect(self):
373 """Initiate a connection to the simulator."""
374 self._handler.connect()
375 self._startDefaultNormal()
376
377 def startMonitoring(self):
378 """Start the periodic monitoring of the aircraft and pass the resulting
379 state to the aircraft object periodically."""
380 assert not self._monitoringRequested
381 self._monitoringRequested = True
382
383 def stopMonitoring(self):
384 """Stop the periodic monitoring of the aircraft."""
385 assert self._monitoringRequested
386 self._monitoringRequested = False
387
388 def disconnect(self):
389 """Disconnect from the simulator."""
390 assert not self._monitoringRequested
391
392 self._stopNormal()
393 self._handler.disconnect()
394
395 def _startDefaultNormal(self):
396 """Start the default normal periodic request."""
397 assert self._normalRequestID is None
398 self._normalRequestID = self._handler.requestPeriodicRead(1.0,
399 Simulator.normalData,
400 self._handleNormal)
401
402 def _stopNormal(self):
403 """Stop the normal period request."""
404 assert self._normalRequestID is not None
405 self._handler.clearPeriodic(self._normalRequestID)
406 self._normalRequestID = None
407
408 def _handleNormal(self, data, extra):
409 """Handle the reply to the normal request.
410
411 At the beginning the result consists the data for normalData. When
412 monitoring is started, it contains the result also for the
413 aircraft-specific values.
414 """
415 timestamp = calendar.timegm(time.struct_time([data[0],
416 1, 1, 0, 0, 0, -1, 1, 0]))
417 timestamp += data[1] * 24 * 3600
418 timestamp += data[2] * 3600
419 timestamp += data[3] * 60
420 timestamp += data[4]
421
422 createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
423
424 if self._monitoringRequested and not self._monitoring:
425 self._monitoring = True
426 self._stopNormal()
427 self._startMonitoring()
428 elif self._monitoring and not self._monitoringRequested:
429 self._monitoring = False
430 self._stopNormal()
431 self._startDefaultNormal()
432 elif self._monitoring and self._aircraftModel is not None and \
433 not createdNewModel:
434 aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
435 timestamp, data)
436 self._aircraft.handleState(aircraftState)
437
438 def _setAircraftName(self, timestamp, name, airPath):
439 """Set the name of the aicraft and if it is different from the
440 previous, create a new model for it.
441
442 If so, also notifty the aircraft about the change.
443
444 Return if a new model was created."""
445 aircraftName = (name, airPath)
446 if aircraftName==self._aircraftName:
447 return False
448
449 self._aircraftName = aircraftName
450 needNew = self._aircraftModel is None
451 needNew = needNew or\
452 not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
453 if not needNew:
454 specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
455 needNew = specialModel is not None and \
456 specialModel is not self._aircraftModel.__class__
457
458 if needNew:
459 self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
460
461 self._aircraft.modelChanged(timestamp, name, self._aircraftModel.name)
462
463 return needNew
464
465 def _setAircraftModel(self, model):
466 """Set a new aircraft model.
467
468 It will be queried for the data to monitor and the monitoring request
469 will be replaced by a new one."""
470 self._aircraftModel = model
471
472 if self._monitoring:
473 self._stopNormal()
474 self._startMonitoring()
475
476 def _startMonitoring(self):
477 """Start monitoring with the current aircraft model."""
478 assert self._monitoring
479
480 data = Simulator.normalData[:]
481 self._aircraftModel.addMonitoringData(data)
482
483 self._normalRequestID = \
484 self._handler.requestPeriodicRead(1.0, data,
485 self._handleNormal)
486
487#------------------------------------------------------------------------------
488
489class AircraftModel(object):
490 """Base class for the aircraft models.
491
492 Aircraft models handle the data arriving from FSUIPC and turn it into an
493 object describing the aircraft's state."""
494 monitoringData = [("paused", 0x0264, "H"),
495 ("frozen", 0x3364, "H"),
496 ("replay", 0x0628, "d"),
497 ("slew", 0x05dc, "H"),
498 ("overspeed", 0x036d, "b"),
499 ("stalled", 0x036c, "b"),
500 ("onTheGround", 0x0366, "H"),
501 ("grossWeight", 0x30c0, "f"),
502 ("heading", 0x0580, "d"),
503 ("pitch", 0x0578, "d"),
504 ("bank", 0x057c, "d"),
505 ("ias", 0x02bc, "d"),
506 ("groundSpeed", 0x02b4, "d"),
507 ("vs", 0x02c8, "d"),
508 ("radioAltitude", 0x31e4, "d"),
509 ("altitude", 0x0570, "l"),
510 ("gLoad", 0x11ba, "H"),
511 ("flapsControl", 0x0bdc, "d"),
512 ("flapsLeft", 0x0be0, "d"),
513 ("flapsRight", 0x0be4, "d"),
514 ("lights", 0x0d0c, "H"),
515 ("pitot", 0x029c, "b"),
516 ("parking", 0x0bc8, "H"),
517 ("noseGear", 0x0bec, "d"),
518 ("spoilersArmed", 0x0bcc, "d"),
519 ("spoilers", 0x0bd0, "d"),
520 ("altimeter", 0x0330, "H"),
521 ("nav1", 0x0350, "H"),
522 ("nav2", 0x0352, "H"),
523 ("squawk", 0x0354, "H")]
524
525 specialModels = []
526
527 @staticmethod
528 def registerSpecial(clazz):
529 """Register the given class as a special model."""
530 AircraftModel.specialModels.append(clazz)
531
532 @staticmethod
533 def findSpecial(aircraft, aircraftName):
534 for specialModel in AircraftModel.specialModels:
535 if specialModel.doesHandle(aircraft, aircraftName):
536 return specialModel
537 return None
538
539 @staticmethod
540 def create(aircraft, aircraftName):
541 """Create the model for the given aircraft name, and notify the
542 aircraft about it."""
543 specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
544 if specialModel is not None:
545 return specialModel()
546 if aircraft.type in _genericModels:
547 return _genericModels[aircraft.type]()
548 else:
549 return GenericModel()
550
551 @staticmethod
552 def convertBCD(data, length):
553 """Convert a data item encoded as BCD into a string of the given number
554 of digits."""
555 bcd = ""
556 for i in range(0, length):
557 digit = chr(ord('0') + (data&0x0f))
558 data >>= 4
559 bcd = digit + bcd
560 return bcd
561
562 @staticmethod
563 def convertFrequency(data):
564 """Convert the given frequency data to a string."""
565 bcd = AircraftModel.convertBCD(data, 4)
566 return "1" + bcd[0:2] + "." + bcd[2:4]
567
568 def __init__(self, flapsNotches):
569 """Construct the aircraft model.
570
571 flapsNotches is a list of degrees of flaps that are available on the aircraft."""
572 self._flapsNotches = flapsNotches
573
574 @property
575 def name(self):
576 """Get the name for this aircraft model."""
577 return "FSUIPC/Generic"
578
579 def doesHandle(self, aircraft, aircraftName):
580 """Determine if the model handles the given aircraft name.
581
582 This default implementation returns False."""
583 return False
584
585 def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
586 """Add the given FSUIPC offset and type to the given array and a member
587 attribute with the given name."""
588 dest.append((offset, type))
589 if attrName is not None:
590 setattr(self, attrName, len(dest)-1)
591
592 def _addDataWithIndexMembers(self, dest, prefix, data):
593 """Add FSUIPC data to the given array and also corresponding index
594 member variables with the given prefix.
595
596 data is a list of triplets of the following items:
597 - the name of the data item. The index member variable will have a name
598 created by prepending the given prefix to this name.
599 - the FSUIPC offset
600 - the FSUIPC type
601
602 The latter two items will be appended to dest."""
603 for (name, offset, type) in data:
604 self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
605
606 def addMonitoringData(self, data):
607 """Add the model-specific monitoring data to the given array."""
608 self._addDataWithIndexMembers(data, "_monidx_",
609 AircraftModel.monitoringData)
610
611 def getAircraftState(self, aircraft, timestamp, data):
612 """Get an aircraft state object for the given monitoring data."""
613 state = fs.AircraftState()
614
615 state.timestamp = timestamp
616
617 state.paused = data[self._monidx_paused]!=0 or \
618 data[self._monidx_frozen]!=0 or \
619 data[self._monidx_replay]!=0
620 state.trickMode = data[self._monidx_slew]!=0
621
622 state.overspeed = data[self._monidx_overspeed]!=0
623 state.stalled = data[self._monidx_stalled]!=0
624 state.onTheGround = data[self._monidx_onTheGround]!=0
625
626 state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
627
628 state.heading = data[self._monidx_heading]*360.0/65536.0/65536.0
629 if state.heading<0.0: state.heading += 360.0
630
631 state.pitch = data[self._monidx_pitch]*360.0/65536.0/65536.0
632 state.bank = data[self._monidx_bank]*360.0/65536.0/65536.0
633
634 state.ias = data[self._monidx_ias]/128.0
635 state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
636 state.vs = data[self._monidx_vs]*60.0/const.FEETTOMETRES/256.0
637
638 state.radioAltitude = data[self._monidx_radioAltitude]/const.FEETTOMETRES/65536.0
639 state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
640
641 state.gLoad = data[self._monidx_gLoad] / 625.0
642
643 numNotchesM1 = len(self._flapsNotches) - 1
644 flapsIncrement = 16383 / numNotchesM1
645 flapsControl = data[self._monidx_flapsControl]
646 flapsIndex = flapsControl / flapsIncrement
647 if flapsIndex < numNotchesM1:
648 if (flapsControl - (flapsIndex*flapsIncrement) >
649 (flapsIndex+1)*flapsIncrement - flapsControl):
650 flapsIndex += 1
651 state.flapsSet = self._flapsNotches[flapsIndex]
652
653 flapsLeft = data[self._monidx_flapsLeft]
654 flapsIndex = flapsLeft / flapsIncrement
655 state.flaps = self._flapsNotches[flapsIndex]
656 if flapsIndex != numNotchesM1:
657 thisNotch = flapsIndex * flapsIncrement
658 nextNotch = thisNotch + flapsIncrement
659
660 state.flaps += (self._flapsNotches[flapsIndex+1] - state.flaps) * \
661 (flapsLeft - thisNotch) / (nextNotch - thisNotch)
662
663 lights = data[self._monidx_lights]
664
665 state.navLightsOn = (lights&0x01) != 0
666 state.antiCollisionLightsOn = (lights&0x02) != 0
667 state.landingLightsOn = (lights&0x04) != 0
668 state.strobeLightsOn = (lights&0x10) != 0
669
670 state.pitotHeatOn = data[self._monidx_pitot]!=0
671
672 state.parking = data[self._monidx_parking]!=0
673
674 state.gearsDown = data[self._monidx_noseGear]==16383
675
676 state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
677
678 spoilers = data[self._monidx_spoilers]
679 if spoilers<=4800:
680 state.spoilersExtension = 0.0
681 else:
682 state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
683
684 state.altimeter = data[self._monidx_altimeter] / 16.0
685
686 state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
687 state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
688 state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
689
690 return state
691
692#------------------------------------------------------------------------------
693
694class GenericAircraftModel(AircraftModel):
695 """A generic aircraft model that can handle the fuel levels, the N1 or RPM
696 values and some other common parameters in a generic way."""
697 def __init__(self, flapsNotches, fuelInfo, numEngines, isN1 = True):
698 """Construct the generic aircraft model with the given data.
699
700 flapsNotches is an array of how much degrees the individual flaps
701 notches mean.
702
703 fuelInfo is an array of FSUIPC offsets for the levels of the fuel
704 tanks. It is assumed to be a 4-byte value, followed by another 4-byte
705 value, which is the fuel tank capacity.
706
707 numEngines is the number of engines the aircraft has.
708
709 isN1 determines if the engines have an N1 value or an RPM value
710 (e.g. pistons)."""
711 super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
712
713 self._fuelInfo = fuelInfo
714 self._fuelStartIndex = None
715 self._numEngines = numEngines
716 self._engineStartIndex = None
717 self._isN1 = isN1
718
719 def doesHandle(self, aircraft, aircraftName):
720 """Determine if the model handles the given aircraft name.
721
722 This implementation returns True."""
723 return True
724
725 def addMonitoringData(self, data):
726 """Add the model-specific monitoring data to the given array."""
727 super(GenericAircraftModel, self).addMonitoringData(data)
728
729 self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
730
731 self._fuelStartIndex = len(data)
732 for offset in self._fuelInfo:
733 self._addOffsetWithIndexMember(data, offset, "u") # tank level
734 self._addOffsetWithIndexMember(data, offset+4, "u") # tank capacity
735
736 if self._isN1:
737 self._engineStartIndex = len(data)
738 for i in range(0, self._numEngines):
739 self._addOffsetWithIndexMember(data, 0x0898 + i * 0x98, "u") # N1
740 self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "d") # throttle lever
741
742 def getAircraftState(self, aircraft, timestamp, data):
743 """Get the aircraft state.
744
745 Get it from the parent, and then add the data about the fuel levels and
746 the engine parameters."""
747 state = super(GenericAircraftModel, self).getAircraftState(aircraft,
748 timestamp,
749 data)
750
751 fuelWeight = data[self._monidx_fuelWeight]/256.0
752 state.fuel = []
753 for i in range(self._fuelStartIndex,
754 self._fuelStartIndex + 2*len(self._fuelInfo), 2):
755 fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
756 state.fuel.append(fuel)
757
758 state.n1 = []
759 state.reverser = []
760 for i in range(self._engineStartIndex,
761 self._engineStartIndex + 2*self._numEngines, 2):
762 state.n1.append(data[i]*100.0/16384.0)
763 state.reverser.append(data[i+1]<0)
764
765 return state
766
767#------------------------------------------------------------------------------
768
769class GenericModel(GenericAircraftModel):
770 """Generic aircraft model for an unknown type."""
771 def __init__(self):
772 """Construct the model."""
773 super(GenericModel, self). \
774 __init__(flapsNotches = [0, 10, 20, 30],
775 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
776 numEngines = 2)
777
778 @property
779 def name(self):
780 """Get the name for this aircraft model."""
781 return "FSUIPC/Generic"
782
783#------------------------------------------------------------------------------
784
785class B737Model(GenericAircraftModel):
786 """Generic model for the Boeing 737 Classing and NG aircraft."""
787 def __init__(self):
788 """Construct the model."""
789 super(B737Model, self). \
790 __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
791 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
792 numEngines = 2)
793
794 @property
795 def name(self):
796 """Get the name for this aircraft model."""
797 return "FSUIPC/Generic Boeing 737"
798
799#------------------------------------------------------------------------------
800
801class PMDGBoeing737NGModel(B737Model):
802 """A model handler for the PMDG Boeing 737NG model."""
803 @staticmethod
804 def doesHandle(aircraft, (name, airPath)):
805 """Determine if this model handler handles the aircraft with the given
806 name."""
807 return aircraft.type in [const.AIRCRAFT_B736,
808 const.AIRCRAFT_B737,
809 const.AIRCRAFT_B738] and \
810 (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
811 (name.find("737")!=-1 or airPath.find("737")!=-1) and \
812 (name.find("600")!=-1 or airPath.find("600")!=-1 or \
813 name.find("700")!=-1 or airPath.find("700")!=-1 or \
814 name.find("800")!=-1 or airPath.find("800")!=-1 or \
815 name.find("900")!=-1 or airPath.find("900")!=-1)
816
817 @property
818 def name(self):
819 """Get the name for this aircraft model."""
820 return "FSUIPC/PMDG Boeing 737NG"
821
822 def addMonitoringData(self, data):
823 """Add the model-specific monitoring data to the given array."""
824 super(PMDGBoeing737NGModel, self).addMonitoringData(data)
825
826 self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
827
828 def getAircraftState(self, aircraft, timestamp, data):
829 """Get the aircraft state.
830
831 Get it from the parent, and then check some PMDG-specific stuff."""
832 state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
833 timestamp,
834 data)
835 if data[self._pmdgidx_switches]&0x01==0x01:
836 state.altimeter = 1013.25
837
838 return state
839
840#------------------------------------------------------------------------------
841
842class B767Model(GenericAircraftModel):
843 """Generic model for the Boeing 767 aircraft."""
844 def __init__(self):
845 """Construct the model."""
846 super(B767Model, self). \
847 __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
848 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
849 numEngines = 2)
850
851 @property
852 def name(self):
853 """Get the name for this aircraft model."""
854 return "FSUIPC/Generic Boeing 767"
855
856#------------------------------------------------------------------------------
857
858class DH8DModel(GenericAircraftModel):
859 """Generic model for the Boeing 737 NG aircraft."""
860 def __init__(self):
861 """Construct the model."""
862 super(DH8DModel, self). \
863 __init__(flapsNotches = [0, 5, 10, 15, 35],
864 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
865 numEngines = 2)
866
867 @property
868 def name(self):
869 """Get the name for this aircraft model."""
870 return "FSUIPC/Generic Bombardier Dash-8 Q400"
871
872#------------------------------------------------------------------------------
873
874class CRJ2Model(GenericAircraftModel):
875 """Generic model for the Bombardier CRJ-200 aircraft."""
876 def __init__(self):
877 """Construct the model."""
878 super(CRJ2Model, self). \
879 __init__(flapsNotches = [0, 8, 20, 30, 45],
880 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
881 numEngines = 2)
882
883 @property
884 def name(self):
885 """Get the name for this aircraft model."""
886 return "FSUIPC/Generic Bombardier CRJ-200"
887
888#------------------------------------------------------------------------------
889
890class F70Model(GenericAircraftModel):
891 """Generic model for the Fokker F70 aircraft."""
892 def __init__(self):
893 """Construct the model."""
894 super(F70Model, self). \
895 __init__(flapsNotches = [0, 8, 15, 25, 42],
896 fuelInfo = [0x0b74, 0x0b7c, 0xb94],
897 numEngines = 2)
898
899 @property
900 def name(self):
901 """Get the name for this aircraft model."""
902 return "FSUIPC/Generic Fokker 70"
903
904#------------------------------------------------------------------------------
905
906class DC3Model(GenericAircraftModel):
907 """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
908 def __init__(self):
909 """Construct the model."""
910 super(DC3Model, self). \
911 __init__(flapsNotches = [0, 15, 30, 45],
912 fuelInfo = [0x0b7c, 0x0b84, 0x0b94, 0x0b9c],
913 numEngines = 2)
914
915 @property
916 def name(self):
917 """Get the name for this aircraft model."""
918 return "FSUIPC/Generic Lisunov Li-2"
919
920#------------------------------------------------------------------------------
921
922class T134Model(GenericAircraftModel):
923 """Generic model for the Tupolev Tu-134 aircraft."""
924 def __init__(self):
925 """Construct the model."""
926 super(T134Model, self). \
927 __init__(flapsNotches = [0, 10, 20, 30],
928 fuelInfo = [0x0b74,
929 0x0b8c, 0x0b84,
930 0x0ba4, 0x0b9c,
931 0x1254, 0x125c],
932 numEngines = 2)
933
934 @property
935 def name(self):
936 """Get the name for this aircraft model."""
937 return "FSUIPC/Generic Tupolev Tu-134"
938
939#------------------------------------------------------------------------------
940
941class T154Model(GenericAircraftModel):
942 """Generic model for the Tupolev Tu-134 aircraft."""
943 def __init__(self):
944 """Construct the model."""
945 super(T154Model, self). \
946 __init__(flapsNotches = [0, 15, 28, 45],
947 fuelInfo = [0x0b74, 0x0b7c, 0x0b94,
948 0x1244, 0x0b84, 0x0b9c],
949 numEngines = 3)
950
951 @property
952 def name(self):
953 """Get the name for this aircraft model."""
954 return "FSUIPC/Generic Tupolev Tu-154"
955
956 def getAircraftState(self, aircraft, timestamp, data):
957 """Get an aircraft state object for the given monitoring data.
958
959 This removes the reverser value for the middle engine."""
960 state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
961 del state.reverser[1]
962 return state
963
964#------------------------------------------------------------------------------
965
966class YK40Model(GenericAircraftModel):
967 """Generic model for the Yakovlev Yak-40 aircraft."""
968 def __init__(self):
969 """Construct the model."""
970 super(YK40Model, self). \
971 __init__(flapsNotches = [0, 20, 35],
972 fuelInfo = [0x0b7c, 0x0b94],
973 numEngines = 2)
974
975 @property
976 def name(self):
977 """Get the name for this aircraft model."""
978 return "FSUIPC/Generic Yakovlev Yak-40"
979
980#------------------------------------------------------------------------------
981
982_genericModels = { const.AIRCRAFT_B736 : B737Model,
983 const.AIRCRAFT_B737 : B737Model,
984 const.AIRCRAFT_B738 : B737Model,
985 const.AIRCRAFT_B733 : B737Model,
986 const.AIRCRAFT_B734 : B737Model,
987 const.AIRCRAFT_B735 : B737Model,
988 const.AIRCRAFT_DH8D : DH8DModel,
989 const.AIRCRAFT_B762 : B767Model,
990 const.AIRCRAFT_B763 : B767Model,
991 const.AIRCRAFT_CRJ2 : B767Model,
992 const.AIRCRAFT_F70 : F70Model,
993 const.AIRCRAFT_DC3 : DC3Model,
994 const.AIRCRAFT_T134 : T134Model,
995 const.AIRCRAFT_T154 : T154Model,
996 const.AIRCRAFT_YK40 : YK40Model }
997
998#------------------------------------------------------------------------------
999
1000AircraftModel.registerSpecial(PMDGBoeing737NGModel)
1001
1002#------------------------------------------------------------------------------
1003
Note: See TracBrowser for help on using the repository browser.