source: src/mlx/pyuipc_sim.py@ 417:391bc4efe7b8

Last change on this file since 417:391bc4efe7b8 was 408:1e2202fe006b, checked in by István Váradi <ivaradi@…>, 11 years ago

Added support for querying, displaying and simulating the QNH value (re #175)

File size: 70.8 KB
Line 
1
2import const
3
4import cmd
5import threading
6import socket
7import time
8import calendar
9import sys
10import struct
11import cPickle
12import math
13
14#------------------------------------------------------------------------------
15
16## @package mlx.pyuipc_sim
17#
18# Simulator of the PyUIPC module.
19#
20# This is a simulation of the PyUIPC module emulating offsets that are needed
21# by the logger.
22#
23# This module can also be run as a program, in which case it connects to an
24# already running logger (that uses this module in place of the real pyuipc),
25# and various commands can be given to query or modify the values of the
26# offsets. There is a 'help' command and completion also works.
27#
28# This module is used instead of the real PyUIPC module, if the program is not
29# running on Windows or the FORCE_PYUIPC_SIM environment variable is present.
30
31#------------------------------------------------------------------------------
32#------------------------------------------------------------------------------
33
34## Version constants
35SIM_ANY=0
36SIM_FS98=1
37SIM_FS2K=2
38SIM_CFS2=3
39SIM_CFS1=4
40SIM_FLY=5
41SIM_FS2K2=6
42SIM_FS2K4=7
43SIM_FSX=8
44
45#------------------------------------------------------------------------------
46
47## Error constants
48ERR_OK=0
49ERR_OPEN=1
50ERR_NOFS=2
51ERR_REGMSG=3
52ERR_ATOM=4
53ERR_MAP=5
54ERR_VIEW=6
55ERR_VERSION=7
56ERR_WRONGFS=8
57ERR_NOTOPEN=9
58ERR_NODATA=10
59ERR_TIMEOUT=11
60ERR_SENDMSG=12
61ERR_DATA=13
62ERR_RUNNING=14
63ERR_SIZE=15
64
65#------------------------------------------------------------------------------
66
67## The version of FSUIPC
68fsuipc_version=0x0401
69lib_version=0x0302
70fs_version=SIM_FS2K4
71
72#------------------------------------------------------------------------------
73
74class FSUIPCException(Exception):
75 """FSUIPC exception class.
76
77 It contains a member variable named errorCode. The string is a text
78 describing the error."""
79
80 errors=["OK",
81 "Attempt to Open when already Open",
82 "Cannot link to FSUIPC or WideClient",
83 "Failed to Register common message with Windows",
84 "Failed to create Atom for mapping filename",
85 "Failed to create a file mapping object",
86 "Failed to open a view to the file map",
87 "Incorrect version of FSUIPC, or not FSUIPC",
88 "Sim is not version requested",
89 "Call cannot execute, link not Open",
90 "Call cannot execute: no requests accumulated",
91 "IPC timed out all retries",
92 "IPC sendmessage failed all retries",
93 "IPC request contains bad data",
94 "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
95 "Read or Write request cannot be added, memory for Process is full"]
96
97 def __init__(self, errorCode):
98 """
99 Construct the exception
100 """
101 if errorCode<len(self.errors):
102 self.errorString = self.errors[errorCode]
103 else:
104 self.errorString = "Unknown error"
105 Exception.__init__(self, self.errorString)
106 self.errorCode = errorCode
107
108 def __str__(self):
109 """
110 Convert the excption to string
111 """
112 return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
113
114#------------------------------------------------------------------------------
115
116class Values(object):
117 """The values that can be read from 'FSUIPC'."""
118 ## Fuel data index: centre tank
119 FUEL_CENTRE = 0
120
121 ## Fuel data index: left main tank
122 FUEL_LEFT = 1
123
124 ## Fuel data index: right main tank
125 FUEL_RIGHT = 2
126
127 ## Fuel data index: left aux tank
128 FUEL_LEFT_AUX = 3
129
130 ## Fuel data index: right aux tank
131 FUEL_RIGHT_AUX = 4
132
133 ## Fuel data index: left tip tank
134 FUEL_LEFT_TIP = 5
135
136 ## Fuel data index: right tip tank
137 FUEL_RIGHT_TIP = 6
138
139 ## Fuel data index: external 1 tank
140 FUEL_EXTERNAL_1 = 7
141
142 ## Fuel data index: external 2 tank
143 FUEL_EXTERNAL_2 = 8
144
145 ## Fuel data index: centre 2 tank
146 FUEL_CENTRE_2 = 9
147
148 ## The number of fuel tank entries
149 NUM_FUEL = FUEL_CENTRE_2 + 1
150
151 ## Engine index: engine #1
152 ENGINE_1 = 0
153
154 ## Engine index: engine #2
155 ENGINE_2 = 1
156
157 ## Engine index: engine #3
158 ENGINE_3 = 2
159
160 ## The number of hotkey entries
161 HOTKEY_SIZE = 56
162
163 @staticmethod
164 def _readFrequency(frequency):
165 """Convert the given frequency into BCD."""
166 return Values._readBCD(int(frequency*100.0))
167
168 @staticmethod
169 def _readADFFrequency(frequency):
170 """Convert the given frequency into a tuple of ADF BCD frequency
171 components."""
172 mainFrequency = Values._readBCD(int(math.floor(frequency))%1000)
173 extFrequency = (int(frequency/1000.0)*256) + (int(frequency*10.0)%10)
174
175 return [mainFrequency, extFrequency]
176
177 @staticmethod
178 def _readBCD(value):
179 """Convert the given value into BCD format."""
180 bcd = (value/1000) % 10
181 bcd <<= 4
182 bcd |= (value/100) % 10
183 bcd <<= 4
184 bcd |= (value/10) % 10
185 bcd <<= 4
186 bcd |= value % 10
187 return bcd
188
189 @staticmethod
190 def _writeFrequency(value):
191 """Convert the given value into a frequency."""
192 return (Values._writeBCD(value) + 10000) / 100.0
193
194 @staticmethod
195 def _toADFFrequency(main, ext):
196 """Convert the given values into an ADF frequency."""
197 frequency = Values._writeBCD(main)
198 frequency += 1000.0*int(ext/256)
199 frequency += (int(ext)%16)/10.0
200 return frequency
201
202 @staticmethod
203 def _writeADFFrequency(current, value, isMain):
204 """Convert the given part of an ADF frequency into the
205 whole frequency."""
206 [mainFrequency, extFrequency] = Values._readADFFrequency(current)
207 if isMain: mainFrequency = value
208 else: extFrequency = value
209
210 return Values._toADFFrequency(mainFrequency, extFrequency)
211
212 @staticmethod
213 def _writeBCD(value):
214 """Convert the given BCD value into a real value."""
215 bcd = (value>>12) & 0x0f
216 bcd *= 10
217 bcd += (value>>8) & 0x0f
218 bcd *= 10
219 bcd += (value>>4) & 0x0f
220 bcd *= 10
221 bcd += (value>>0) & 0x0f
222
223 return bcd
224
225 def __init__(self):
226 """Construct the values with defaults."""
227 self._timeOffset = 0
228 self.airPath = "C:\\Program Files\\Microsoft Games\\" \
229 "FS9\\Aircraft\\Cessna\\cessna172.air"
230 self.aircraftName = "Cessna 172SP"
231 self.flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40]
232 self.fuelCapacities = [10000.0, 5000.0, 5000.0, 5000.0, 5000.0,
233 5000.0, 5000.0, 5000.0, 5000.0, 5000.0]
234
235 self.latitude = 47.5
236 self.longitude = 19.05
237
238 self.paused = False
239 self.frozen = False
240 self.replay = False
241 self.slew = False
242 self.overspeed = False
243 self.stalled = False
244 self.onTheGround = True
245
246 self.zfw = 50000.0
247
248 self.fuelWeights = [0.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
249 # Wikipedia: "Jet fuel", Jet A-1 density at 15*C .804kg/l -> 6.7 pounds/gallon
250 self.fuelWeight = 6.70970518
251
252 self.heading = 220.0
253 self.pitch = 0.0
254 self.bank = 0.0
255
256 self.ias = 0.0
257 self.vs = 0.0
258 self.tdRate = 0.0
259
260 self.radioAltitude = None
261 self.altitude = 513.0
262
263 self.gLoad = 1.0
264
265 self.flapsControl = 0.0
266 self.flaps = 0.0
267
268 self.navLightsOn = True
269 self.antiCollisionLightsOn = False
270 self.landingLightsOn = False
271 self.strobeLightsOn = False
272
273 self.pitot = False
274 self.parking = True
275
276 self.gearControl = 1.0
277 self.noseGear = 1.0
278
279 self.spoilersArmed = False
280 self.spoilers = 0.0
281
282 self.altimeter = 1013.0
283 self.qnh = 1011.2
284
285 self.nav1 = 117.3
286 self.nav1_obs = 128
287 self.nav2 = 109.5
288 self.nav2_obs = 308
289 self.adf1 = 382.7
290 self.adf2 = 1540.6
291 self.squawk = 2200
292
293 self.windSpeed = 8.0
294 self.windDirection = 300.0
295 self.visibility = 10000
296
297 self.n1 = [0.0, 0.0, 0.0]
298 self.throttles = [0.0, 0.0, 0.0]
299
300 self.payloadCount = 1
301 self.payload = []
302 for i in range(0, 61): self.payload.append(0.0)
303
304 self.textScrolling = False
305 self.message = ""
306 self.messageDuration = 0
307
308 self.hotkeyTable = []
309 for i in range(0, Values.HOTKEY_SIZE):
310 self.hotkeyTable.append([0, 0, 0, 0])
311
312 self.cog = 0.27
313
314 self.pmdg_737ng_switches = 0
315 self.pmdg_737ngx_lts_positionsw = 0
316
317 self.xpdrC = False
318
319 self.apMaster = False
320 self.apHeadingHold = False
321 self.apHeading = 124
322 self.apAltitudeHold = False
323 self.apAltitude = 7000
324
325 self.elevatorTrim = 0.0
326
327 self.eng1DeIce = False
328 self.eng2DeIce = False
329 self.eng3DeIce = False
330 self.propDeIce = False
331 self.structDeIce = False
332
333 def read(self, offset, type):
334 """Read the value at the given offset."""
335 try:
336 return self._read(offset, type)
337 except Exception, e:
338 print "failed to read offset %04x: %s" % (offset, str(e))
339 raise FSUIPCException(ERR_DATA)
340
341 def _read(self, offset, type):
342 """Read the value at the given offset."""
343 if offset==0x023a: # Second of time
344 return self._readUTC().tm_sec
345 elif offset==0x023b: # Hour of Zulu time
346 return self._readUTC().tm_hour
347 elif offset==0x023c: # Minute of Zulu time
348 return self._readUTC().tm_min
349 elif offset==0x023e: # Day number on year
350 return self._readUTC().tm_yday
351 elif offset==0x0240: # Year in FS
352 return self._readUTC().tm_year
353 elif offset==0x0264: # Paused
354 return 1 if self.paused else 0
355 elif offset==0x029c: # Pitot
356 return 1 if self.pitot else 0
357 elif offset==0x02b4: # Ground speed
358 # FIXME: calculate TAS first, then from the heading and
359 # wind the GS
360 return int(self.ias * 65536.0 * 1852.0 / 3600.0)
361 elif offset==0x02b8: # TAS
362 return int(self._getTAS() * 128.0)
363 elif offset==0x02bc: # IAS
364 return int(self.ias * 128.0)
365 elif offset==0x02c8: # VS
366 return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
367 elif offset==0x02d4: # ADF2 main
368 return Values._readADFFrequency(self.adf2)[0]
369 elif offset==0x02d6: # ADF2 extended
370 return Values._readADFFrequency(self.adf2)[1]
371 elif offset==0x030c: # TD rate
372 return int(self.tdRate * const.FEETTOMETRES * 256.0 / 60.0)
373 elif offset==0x0330: # Altimeter
374 return int(self.altimeter * 16.0)
375 elif offset==0x034c: # ADF1 main
376 return Values._readADFFrequency(self.adf1)[0]
377 elif offset==0x0350: # NAV1
378 return Values._readFrequency(self.nav1)
379 elif offset==0x0352: # NAV2
380 return Values._readFrequency(self.nav2)
381 elif offset==0x0354: # Squawk
382 return Values._readBCD(self.squawk)
383 elif offset==0x0356: # ADF1 extended
384 return Values._readADFFrequency(self.adf1)[1]
385 elif offset==0x0366: # On the ground
386 return 1 if self.onTheGround else 0
387 elif offset==0x036c: # Stalled
388 return 1 if self.stalled else 0
389 elif offset==0x036d: # Overspeed
390 return 1 if self.overspeed else 0
391 elif offset==0x0560: # Latitude
392 return long(self.latitude * 10001750.0 * 65536.0 * 65536.0 / 90.0)
393 elif offset==0x0568: # Longitude
394 return long(self.longitude * 65536.0 * 65536.0 * 65536.0 * 65536.0 / 360.0)
395 elif offset==0x0570: # Altitude
396 return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
397 elif offset==0x0578: # Pitch
398 return int(self.pitch * 65536.0 * 65536.0 / 360.0)
399 elif offset==0x057c: # Bank
400 return int(self.bank * 65536.0 * 65536.0 / 360.0)
401 elif offset==0x0580: # Heading
402 return int(self.heading * 65536.0 * 65536.0 / 360.0)
403 elif offset==0x05dc: # Slew
404 return 1 if self.slew else 0
405 elif offset==0x0628: # Replay
406 return 1 if self.replay else 0
407 elif offset==0x07bc: # AP Master switch
408 return 1 if self.apMaster else 0
409 elif offset==0x07c8: # AP heading hold
410 return 1 if self.apHeadingHold else 0
411 elif offset==0x07cc: # AP heading
412 return int(self.apHeading * 65536.0 / 360.0)
413 elif offset==0x07d0: # AP altitude hold
414 return 1 if self.apAltitudeHold else 0
415 elif offset==0x07d4: # AP altitude
416 return int(self.apAltitude * const.FEETTOMETRES * 65536.0)
417 elif offset==0x088c: # Engine #1 throttle
418 return self._getThrottle(self.ENGINE_1)
419 elif offset==0x08b2: # Engine #1 de-ice
420 return 1 if self.eng1DeIce else 0
421 elif offset==0x0924: # Engine #2 throttle
422 return self._getThrottle(self.ENGINE_2)
423 elif offset==0x094a: # Engine #2 de-ice
424 return 1 if self.eng2DeIce else 0
425 elif offset==0x09bc: # Engine #3 throttle
426 return self._getThrottle(self.ENGINE_3)
427 elif offset==0x09e2: # Engine #3 de-ice
428 return 1 if self.eng3DeIce else 0
429 elif offset==0x0af4: # Fuel weight
430 return int(self.fuelWeight * 256.0)
431 elif offset==0x0b74: # Centre tank level
432 return self._getFuelLevel(self.FUEL_CENTRE)
433 elif offset==0x0b78: # Centre tank capacity
434 return self._getFuelCapacity(self.FUEL_CENTRE)
435 elif offset==0x0b7c: # Left tank level
436 return self._getFuelLevel(self.FUEL_LEFT)
437 elif offset==0x0b80: # Left tank capacity
438 return self._getFuelCapacity(self.FUEL_LEFT)
439 elif offset==0x0b84: # Left aux tank level
440 return self._getFuelLevel(self.FUEL_LEFT_AUX)
441 elif offset==0x0b88: # Left aux tank capacity
442 return self._getFuelCapacity(self.FUEL_LEFT_AUX)
443 elif offset==0x0b8c: # Left tip tank level
444 return self._getFuelLevel(self.FUEL_LEFT_TIP)
445 elif offset==0x0b90: # Left tip tank capacity
446 return self._getFuelCapacity(self.FUEL_LEFT_TIP)
447 elif offset==0x0b94: # Right aux tank level
448 return self._getFuelLevel(self.FUEL_RIGHT)
449 elif offset==0x0b98: # Right aux tank capacity
450 return self._getFuelCapacity(self.FUEL_RIGHT)
451 elif offset==0x0b9c: # Right tank level
452 return self._getFuelLevel(self.FUEL_RIGHT_AUX)
453 elif offset==0x0ba0: # Right tank capacity
454 return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
455 elif offset==0x0ba4: # Right tip tank level
456 return self._getFuelLevel(self.FUEL_RIGHT_TIP)
457 elif offset==0x0ba8: # Right tip tank capacity
458 return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
459 elif offset==0x0bc8: # Parking
460 return 1 if self.parking else 0
461 elif offset==0x0bcc: # Spoilers armed
462 return 1 if self.spoilersArmed else 0
463 elif offset==0x0bd0: # Spoilers
464 return 0 if self.spoilers == 0 \
465 else int(self.spoilers * (16383 - 4800) + 4800)
466 elif offset==0x0bdc: # Flaps control
467 numNotchesM1 = len(self.flapsNotches) - 1
468 flapsIncrement = 16383.0 / numNotchesM1
469 index = 0
470 while index<numNotchesM1 and \
471 self.flapsControl>self.flapsNotches[index]:
472 index += 1
473
474 if index==numNotchesM1:
475 return 16383
476 else:
477 return int(index * flapsIncrement +
478 (self.flapsControl-self.flapsNotches[index]) *
479 flapsIncrement /
480 (self.flapsNotches[index+1] - self.flapsNotches[index]))
481 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
482 return self.flaps * 16383.0 / self.flapsNotches[-1]
483 elif offset==0x0be8: # Gear control
484 return int(self.gearControl * 16383.0)
485 elif offset==0x0bec: # Nose gear
486 return int(self.noseGear * 16383.0)
487 elif offset==0x0c4e: # NAV1 OBS
488 return self.nav1_obs
489 elif offset==0x0c5e: # NAV2 OBS
490 return self.nav2_obs
491 elif offset==0x0d0c: # Lights
492 lights = 0
493 if self.navLightsOn: lights |= 0x01
494 if self.antiCollisionLightsOn: lights |= 0x02
495 if self.landingLightsOn: lights |= 0x04
496 if self.strobeLightsOn: lights |= 0x10
497 return lights
498 elif offset==0x0e8a: # Visibility
499 return int(self.visibility * 100.0 / 1609.344)
500 elif offset==0x0e90: # Wind speed
501 return int(self.windSpeed)
502 elif offset==0x0e92: # Wind direction
503 return int(self.windDirection * 65536.0 / 360.0)
504 elif offset==0x0ec6: # QNH
505 return int(self.qnh * 16.0)
506 elif offset==0x11ba: # G-Load
507 return int(self.gLoad * 625.0)
508 elif offset==0x11c6: # Mach
509 # FIXME: calculate from IAS, altitude and QNH
510 return int(self.ias * 0.05 * 20480.)
511 elif offset==0x1244: # Centre 2 tank level
512 return self._getFuelLevel(self.FUEL_CENTRE_2)
513 elif offset==0x1248: # Centre 2 tank capacity
514 return self._getFuelCapacity(self.FUEL_CENTRE_2)
515 elif offset==0x1254: # External 1 tank level
516 return self._getFuelLevel(self.FUEL_EXTERNAL_1)
517 elif offset==0x1258: # External 1 tank capacity
518 return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
519 elif offset==0x125c: # External 2 tank level
520 return self._getFuelLevel(self.FUEL_EXTERNAL_2)
521 elif offset==0x1260: # External 2 tank capacity
522 return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
523 elif offset==0x1274: # Text display mode
524 return 1 if self.textScrolling else 0
525 elif offset==0x13fc: # The number of the payload stations
526 return self.payloadCount
527 elif offset>=0x1400 and offset<=0x1f40 and \
528 ((offset-0x1400)%48)==0: # Payload
529 return self.payload[ (offset - 0x1400) / 48 ]
530 elif offset==0x2000: # Engine #1 N1
531 return self.n1[self.ENGINE_1]
532 elif offset==0x2100: # Engine #2 N1
533 return self.n1[self.ENGINE_2]
534 elif offset==0x2200: # Engine #3 N1
535 return self.n1[self.ENGINE_3]
536 elif offset==0x2ea0: # Elevator trim
537 return self.elevatorTrim * math.pi / 180.0
538 elif offset==0x2ef8: # Centre of Gravity
539 return self.cog
540 elif offset==0x30c0: # Gross weight
541 return (self.zfw + sum(self.fuelWeights)) * const.KGSTOLB
542 elif offset==0x31e4: # Radio altitude
543 # FIXME: if self.radioAltitude is None, calculate from the
544 # altitude with some, perhaps random, ground altitude
545 # value
546 radioAltitude = (self.altitude - 517) \
547 if self.radioAltitude is None else self.radioAltitude
548 return (radioAltitude * const.FEETTOMETRES * 65536.0)
549 elif offset==0x320c:
550 return Values.HOTKEY_SIZE
551 elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
552 tableOffset = offset - 0x3210
553 hotkeyIndex = tableOffset / 4
554 index = tableOffset % 4
555 if type=="b" or type=="c":
556 return self.hotkeyTable[hotkeyIndex][index]
557 elif type=="d" or type=="u":
558 if index==0:
559 hotkey = self.hotkeyTable[hotkeyIndex]
560 value = hotkey[3]
561 value <<= 8
562 value |= hotkey[2]
563 value <<= 8
564 value |= hotkey[1]
565 value <<= 8
566 value |= hotkey[0]
567 return value
568 else:
569 print "Unhandled offset: %04x" % (offset,)
570 raise FSUIPCException(ERR_DATA)
571 else:
572 print "Unhandled offset: %04x" % (offset,)
573 raise FSUIPCException(ERR_DATA)
574 elif offset==0x32fa: # Message duration
575 return self.messageDuration
576 elif offset==0x337c: # Prop de-ice
577 return 1 if self.propDeIce else 0
578 elif offset==0x337d: # Structural de-ice
579 return 1 if self.structDeIce else 0
580 elif offset==0x3380: # Message
581 return self.message
582 elif offset==0x3364: # Frozen
583 return 1 if self.frozen else 0
584 elif offset==0x3bfc: # ZFW
585 return int(self.zfw * 256.0 * const.KGSTOLB)
586 elif offset==0x3c00: # Path of the current AIR file
587 return self.airPath
588 elif offset==0x3d00: # Name of the current aircraft
589 return self.aircraftName
590 elif offset==0x6202: # PMDG 737NG switches
591 return self.pmdg_737ng_switches
592 elif offset==0x6500: # PMDG 737NGX lights position SW
593 return self.pmdg_737ngx_lts_positionsw
594 elif offset==0x7b91: # Transponder standby
595 return 0 if self.xpdrC else 1
596 else:
597 print "Unhandled offset: %04x" % (offset,)
598 raise FSUIPCException(ERR_DATA)
599
600 def write(self, offset, value, type):
601 """Write the value at the given offset."""
602 try:
603 return self._write(offset, value, type)
604 except Exception, e:
605 print "failed to write offset %04x: %s" % (offset, str(e))
606 raise FSUIPCException(ERR_DATA)
607
608 def _write(self, offset, value, type):
609 """Write the given value at the given offset."""
610 if offset==0x023a: # Second of time
611 self._updateTimeOffset(5, value)
612 elif offset==0x023b: # Hour of Zulu time
613 self._updateTimeOffset(3, value)
614 elif offset==0x023c: # Minute of Zulu time
615 self._updateTimeOffset(4, value)
616 elif offset==0x023e: # Day number in the year
617 self._updateTimeOffset(7, value)
618 elif offset==0x0240: # Year in FS
619 self._updateTimeOffset(0, value)
620 elif offset==0x0264: # Paused
621 self.paused = value!=0
622 elif offset==0x029c: # Pitot
623 self.pitot = value!=0
624 elif offset==0x02b4: # Ground speed
625 # FIXME: calculate TAS using the heading and the wind, and
626 # then IAS based on the altitude
627 self.ias = value * 3600.0 / 65536.0 / 1852.0
628 elif offset==0x02bc: # IAS
629 self.ias = value / 128.0
630 elif offset==0x02c8: # VS
631 self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
632 if not self.onTheGround:
633 self.tdRate = self.vs
634 elif offset==0x02d4: # ADF2 main
635 self.adf2 = self._writeADFFrequency(self.adf2, value, True)
636 elif offset==0x02d6: # ADF2 main
637 self.adf2 = self._writeADFFrequency(self.adf2, value, False)
638 elif offset==0x0330: # Altimeter
639 self.altimeter = value / 16.0
640 elif offset==0x034c: # ADF1 main
641 self.adf1 = self._writeADFFrequency(self.adf1, value, True)
642 elif offset==0x0350: # NAV1
643 self.nav1 = Values._writeFrequency(value)
644 elif offset==0x0352: # NAV2
645 self.nav2 = Values._writeFrequency(value)
646 elif offset==0x0354: # Squawk
647 self.squawk = Values._writeBCD(value)
648 elif offset==0x0356: # ADF1 ext
649 self.adf1 = self._writeADFFrequency(self.adf1, value, False)
650 elif offset==0x0366: # On the groud
651 self.onTheGround = value!=0
652 if not self.onTheGround:
653 self.tdRate = self.vs
654 elif offset==0x036c: # Stalled
655 self.stalled = value!=0
656 elif offset==0x036d: # Overspeed
657 self.overspeed = value!=0
658 elif offset==0x0560: # Latitude
659 self.latitude = value * 90.0 / 10001750.0 / 65536.0 / 65536.0
660 elif offset==0x0568: # Longitude
661 self.longitude = value * 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
662 elif offset==0x0570: # Altitude
663 self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
664 self.radioAltitude = self.altitude - 517
665 elif offset==0x0578: # Pitch
666 self.pitch = value * 360.0 / 65536.0 / 65536.0
667 elif offset==0x057c: # Bank
668 self.bank = value * 360.0 / 65536.0 / 65536.0
669 elif offset==0x0580: # Heading
670 self.heading = value * 360.0 / 65536.0 / 65536.0
671 elif offset==0x05dc: # Slew
672 self.slew = value!=0
673 elif offset==0x0628: # Replay
674 self.replay = value!=0
675 elif offset==0x07bc: # AP Master switch
676 self.apMaster = value!=0
677 elif offset==0x07c8: # AP heading hold
678 self.apHeadingHold = value!=0
679 elif offset==0x07cc: # AP heading
680 self.apHeading = value * 360.0 / 65536.0
681 elif offset==0x07d0: # AP altitude hold
682 self.apAltitudeHold = value!=0
683 elif offset==0x07d4: # AP altitude
684 self.apAltitude = value / const.FEETTOMETRES / 65536.0
685 elif offset==0x088c: # Engine #1 throttle
686 self._setThrottle(self.ENGINE_1, value)
687 elif offset==0x08b2: # Engine #1 de-ice
688 self.eng1DeIce = value!=0
689 elif offset==0x0924: # Engine #2 throttle
690 self._setThrottle(self.ENGINE_2, value)
691 elif offset==0x094a: # Engine #2 de-ice
692 self.eng2DeIce = value!=0
693 elif offset==0x09bc: # Engine #3 throttle
694 self._setThrottle(self.ENGINE_3, value)
695 elif offset==0x09e2: # Engine #3 de-ice
696 self.eng3DeIce = value!=0
697 elif offset==0x0af4: # Fuel weight
698 self.fuelWeight = value / 256.0
699 elif offset==0x0b74: # Centre tank level
700 self._setFuelLevel(self.FUEL_CENTRE, value)
701 elif offset==0x0b78: # Centre tank capacity
702 self._setFuelCapacity(self.FUEL_CENTRE, value)
703 elif offset==0x0b7c: # Left tank level
704 self._setFuelLevel(self.FUEL_LEFT, value)
705 elif offset==0x0b80: # Left tank capacity
706 self._setFuelCapacity(self.FUEL_LEFT, value)
707 elif offset==0x0b84: # Left aux tank level
708 self._setFuelLevel(self.FUEL_LEFT_AUX, value)
709 elif offset==0x0b88: # Left aux tank capacity
710 self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
711 elif offset==0x0b8c: # Left tip tank level
712 self._setFuelLevel(self.FUEL_LEFT_TIP, value)
713 elif offset==0x0b90: # Left tip tank capacity
714 self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
715 elif offset==0x0b94: # Right aux tank level
716 self._setFuelLevel(self.FUEL_RIGHT, value)
717 elif offset==0x0b98: # Right aux tank capacity
718 self._setFuelCapacity(self.FUEL_RIGHT, value)
719 elif offset==0x0b9c: # Right tank level
720 self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
721 elif offset==0x0ba0: # Right tank capacity
722 self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
723 elif offset==0x0ba4: # Right tip tank level
724 self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
725 elif offset==0x0ba8: # Right tip tank capacity
726 self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
727 elif offset==0x0bc8: # Parking
728 self.parking = value!=0
729 elif offset==0x0bcc: # Spoilers armed
730 self.spoilersArmed = value!=0
731 elif offset==0x0bd0: # Spoilers
732 self.spoilters = 0 if value==0 \
733 else (value - 4800) / (16383 - 4800)
734 elif offset==0x0bdc: # Flaps control
735 numNotchesM1 = len(self.flapsNotches) - 1
736 flapsIncrement = 16383.0 / numNotchesM1
737 index = int(value / flapsIncrement)
738 if index>=numNotchesM1:
739 self.flapsControl = self.flapsNotches[-1]
740 else:
741 self.flapsControl = self.flapsNotches[index]
742 self.flapsControl += (value - index * flapsIncrement) * \
743 (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
744 flapsIncrement
745 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
746 self.flaps = value * self.flapsNotches[-1] / 16383.0
747 elif offset==0x0be8: # Gear control
748 self.gearControl = value / 16383.0
749 elif offset==0x0bec: # Nose gear
750 self.noseGear = value / 16383.0
751 elif offset==0x0c4e: # NAV1 OBS
752 self.nav1_obs = value
753 elif offset==0x0c5e: # NAV2 OBS
754 self.nav2_obs = value
755 elif offset==0x0d0c: # Lights
756 self.navLightsOn = (value&0x01)!=0
757 self.antiCollisionLightsOn = (value&0x02)!=0
758 self.landingLightsOn = (value&0x04)!=0
759 self.strobeLightsOn = (value&0x10)!=0
760 elif offset==0x0e8a: # Visibility
761 self.visibility = value * 1609.344 / 100.0
762 elif offset==0x0e90: # Wind speed
763 self.windSpeed = value
764 elif offset==0x0e92: # Wind direction
765 self.windDirection = value * 360.0 / 65536.0
766 elif offset==0x0ec6: # QNH
767 self.qnh = value / 16.0
768 elif offset==0x11ba: # G-Load
769 self.gLoad = value / 625.0
770 elif offset==0x11c6: # Mach
771 # FIXME: calculate IAS using the altitude and QNH
772 self.ias = value / 0.05 / 20480
773 elif offset==0x1244: # Centre 2 tank level
774 self._setFuelLevel(self.FUEL_CENTRE_2, value)
775 elif offset==0x1248: # Centre 2 tank capacity
776 self._setFuelCapacity(self.FUEL_CENTRE_2, value)
777 elif offset==0x1254: # External 1 tank level
778 self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
779 elif offset==0x1258: # External 1 tank capacity
780 self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
781 elif offset==0x125c: # External 2 tank level
782 self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
783 elif offset==0x1260: # External 2 tank capacity
784 self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
785 elif offset==0x1274: # Text display mode
786 textScrolling = value!=0
787 elif offset==0x13fc: # The number of the payload stations
788 self.payloadCount = int(value)
789 elif offset>=0x1400 and offset<=0x1f40 and \
790 ((offset-0x1400)%48)==0: # Payload
791 self.payload[ (offset - 0x1400) / 48 ] = value
792 elif offset==0x2000: # Engine #1 N1
793 self.n1[self.ENGINE_1] = value
794 elif offset==0x2100: # Engine #2 N1
795 self.n1[self.ENGINE_2] = value
796 elif offset==0x2200: # Engine #3 N1
797 self.n1[self.ENGINE_3] = value
798 elif offset==0x2ea0: # Elevator trim
799 self.elevatorTrim = value * 180.0 / math.pi
800 elif offset==0x2ef8: # Centre of Gravity
801 self.cog = value
802 elif offset==0x30c0: # Gross weight
803 raise FSUIPCException(ERR_DATA)
804 elif offset==0x31e4: # Radio altitude
805 self.radioAltitude = value / const.FEETTOMETRES / 65536.0
806 self.altitude = self.radioAltitude + 517
807 elif offset==0x31e4: # Radio altitude
808 raise FSUIPCException(ERR_DATA)
809 elif offset==0x320c:
810 return Values.HOTKEY_SIZE
811 elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
812 tableOffset = offset - 0x3210
813 hotkeyIndex = tableOffset / 4
814 index = tableOffset % 4
815 if type=="b" or type=="c":
816 self.hotkeyTable[hotkeyIndex][index] = value
817 elif type=="d" or type=="u":
818 if index==0:
819 hotkey = self.hotkeyTable[hotkeyIndex]
820 hotkey[0] = value & 0xff
821 hotkey[1] = (value>>8) & 0xff
822 hotkey[2] = (value>>16) & 0xff
823 hotkey[3] = (value>>24) & 0xff
824 else:
825 print "Unhandled offset: %04x for type '%s'" % (offset, type)
826 raise FSUIPCException(ERR_DATA)
827 else:
828 print "Unhandled offset: %04x for type '%s'" % (offset, type)
829 raise FSUIPCException(ERR_DATA)
830 elif offset==0x32fa: # Message duration
831 self.messageDuration = value
832 elif offset==0x3364: # Frozen
833 self.frozen = value!=0
834 elif offset==0x337c: # Propeller de-ice
835 self.propDeIce = value!=0
836 elif offset==0x337d: # Structural de-ice
837 self.structDeIce = value!=0
838 elif offset==0x3380: # Message
839 self.message = value
840 elif offset==0x3bfc: # ZFW
841 self.zfw = value * const.LBSTOKG / 256.0
842 elif offset==0x3c00: # Path of the current AIR file
843 self.airPath = value
844 elif offset==0x3d00: # Name of the current aircraft
845 self.aircraftName = value
846 elif offset==0x6202: # PMDG 737NG switches
847 self.pmdg_737ng_switches = value
848 elif offset==0x6500: # PMDG 737NGX lights position SW
849 self.pmdg_737ngx_lts_positionsw = value
850 elif offset==0x7b91: # Transponder standby
851 self.xpdrC = value==0
852 else:
853 print "Unhandled offset: %04x" % (offset,)
854 raise FSUIPCException(ERR_DATA)
855
856 def _readUTC(self):
857 """Read the UTC time.
858
859 The current offset is added to it."""
860 return time.gmtime(time.time() + self._timeOffset)
861
862 def _getFuelLevel(self, index):
863 """Get the fuel level for the fuel tank with the given
864 index."""
865 return 0 if self.fuelCapacities[index]==0.0 else \
866 int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
867
868 def _getFuelCapacity(self, index):
869 """Get the capacity of the fuel tank with the given index."""
870 return int(self.fuelCapacities[index] * const.KGSTOLB / self.fuelWeight)
871
872 def _getThrottle(self, index):
873 """Get the throttle value for the given index."""
874 return int(self.throttles[index] * 16383.0)
875
876 def _updateTimeOffset(self, index, value):
877 """Update the time offset if the value in the tm structure is replaced
878 by the given value."""
879 tm = self._readUTC()
880 tm1 = tm[:index] + (value,) + tm[(index+1):]
881 self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
882
883 def _setThrottle(self, index, value):
884 """Set the throttle value for the given index."""
885 self.throttles[index] = value / 16383.0
886
887 def _setFuelLevel(self, index, value):
888 """Set the fuel level for the fuel tank with the given index."""
889 self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
890 65536.0 / 128.0
891
892 def _setFuelCapacity(self, index, value):
893 """Set the capacity of the fuel tank with the given index."""
894 self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
895
896 def _getTAS(self):
897 """Calculate the true airspeed."""
898 pressure = 101325 * math.pow(1 - 2.25577e-5 * self.altitude *
899 const.FEETTOMETRES,
900 5.25588)
901 temperature = 15 - self.altitude * 6.5 * const.FEETTOMETRES / 1000.0
902 temperature += 273.15 # Celsius -> Kelvin
903 airDensity = pressure / (temperature * 287.05)
904 #print "pressure:", pressure, "temperature:", temperature, "airDensity:", airDensity
905 return self.ias * math.sqrt(1.225 / airDensity)
906
907#------------------------------------------------------------------------------
908
909values = Values()
910
911#------------------------------------------------------------------------------
912
913failOpen = False
914
915opened = False
916
917#------------------------------------------------------------------------------
918
919def open(request):
920 """Open the connection."""
921 global opened
922 if failOpen:
923 raise FSUIPCException(ERR_NOFS)
924 elif opened:
925 raise FSUIPCException(ERR_OPEN)
926 else:
927 time.sleep(0.5)
928 opened = True
929 return True
930
931#------------------------------------------------------------------------------
932
933def prepare_data(pattern, forRead = True, checkOpened = True):
934 """Prepare the given pattern for reading and/or writing."""
935 if not checkOpened or opened:
936 return pattern
937 else:
938 raise FSUIPCException(ERR_NOTOPEN)
939
940#------------------------------------------------------------------------------
941
942def read(data, checkOpened = True):
943 """Read the given data."""
944 if not checkOpened or opened:
945 return [values.read(offset, type) for (offset, type) in data]
946 else:
947 raise FSUIPCException(ERR_NOTOPEN)
948
949#------------------------------------------------------------------------------
950
951def write(data, checkOpened = True):
952 """Write the given data."""
953 if not checkOpened or opened:
954 for (offset, type, value) in data:
955 values.write(offset, value, type)
956 else:
957 raise FSUIPCException(ERR_NOTOPEN)
958
959#------------------------------------------------------------------------------
960
961def close():
962 """Close the connection."""
963 global opened
964 opened = False
965
966#------------------------------------------------------------------------------
967
968PORT=15015
969
970CALL_READ=1
971CALL_WRITE=2
972CALL_SETVERSION=3
973CALL_CLOSE=4
974CALL_FAILOPEN=5
975CALL_QUIT = 99
976
977RESULT_RETURNED=1
978RESULT_EXCEPTION=2
979
980#------------------------------------------------------------------------------
981
982class Server(threading.Thread):
983 """The server thread."""
984 def __init__(self):
985 """Construct the thread."""
986 super(Server, self).__init__()
987 self.daemon = True
988
989 def run(self):
990 """Perform the server's operation."""
991 serverSocket = socket.socket()
992
993 serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
994 serverSocket.bind(("", PORT))
995
996 serverSocket.listen(5)
997
998 while True:
999 (clientSocket, clientAddress) = serverSocket.accept()
1000 thread = threading.Thread(target = self._process, args=(clientSocket,))
1001 thread.start()
1002
1003 def _process(self, clientSocket):
1004 """Process the commands arriving on the given socket."""
1005 socketFile = clientSocket.makefile()
1006 try:
1007 while True:
1008 (length,) = struct.unpack("I", clientSocket.recv(4))
1009 data = clientSocket.recv(length)
1010 (call, args) = cPickle.loads(data)
1011 exception = None
1012
1013 try:
1014 if call==CALL_READ:
1015 result = read(args[0], checkOpened = False)
1016 elif call==CALL_WRITE:
1017 result = write(args[0], checkOpened = False)
1018 elif call==CALL_SETVERSION:
1019 global fs_version
1020 fs_version = args[0]
1021 result = None
1022 elif call==CALL_CLOSE:
1023 global opened
1024 opened = False
1025 result = None
1026 elif call==CALL_FAILOPEN:
1027 global failOpen
1028 failOpen = args[0]
1029 result = None
1030 else:
1031 break
1032 except Exception, e:
1033 exception = e
1034
1035 if exception is None:
1036 data = cPickle.dumps((RESULT_RETURNED, result))
1037 else:
1038 data = cPickle.dumps((RESULT_EXCEPTION, str(exception)))
1039 clientSocket.send(struct.pack("I", len(data)) + data)
1040 except Exception, e:
1041 print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
1042 finally:
1043 try:
1044 socketFile.close()
1045 except:
1046 pass
1047 clientSocket.close()
1048
1049#------------------------------------------------------------------------------
1050
1051class Client(object):
1052 """Client to the server."""
1053 def __init__(self, serverHost):
1054 """Construct the client and connect to the given server
1055 host."""
1056 self._socket = socket.socket()
1057 self._socket.connect((serverHost, PORT))
1058
1059 self._socketFile = self._socket.makefile()
1060
1061 def read(self, data):
1062 """Read the given data."""
1063 return self._call(CALL_READ, data)
1064
1065 def write(self, data):
1066 """Write the given data."""
1067 return self._call(CALL_WRITE, data)
1068
1069 def setVersion(self, version):
1070 """Set the FS version to emulate."""
1071 return self._call(CALL_SETVERSION, int(version))
1072
1073 def close(self):
1074 """Close the connection currently opened in the simulator."""
1075 return self._call(CALL_CLOSE, None)
1076
1077 def failOpen(self, really):
1078 """Enable/disable open failure in the simulator."""
1079 return self._call(CALL_FAILOPEN, really)
1080
1081 def quit(self):
1082 """Quit from the simulator."""
1083 data = cPickle.dumps((CALL_QUIT, None))
1084 self._socket.send(struct.pack("I", len(data)) + data)
1085
1086 def _call(self, command, data):
1087 """Perform a call with the given command and data."""
1088 data = cPickle.dumps((command, [data]))
1089 self._socket.send(struct.pack("I", len(data)) + data)
1090 (length,) = struct.unpack("I", self._socket.recv(4))
1091 data = self._socket.recv(length)
1092 (resultCode, result) = cPickle.loads(data)
1093 if resultCode==RESULT_RETURNED:
1094 return result
1095 else:
1096 raise Exception(result)
1097
1098#------------------------------------------------------------------------------
1099#------------------------------------------------------------------------------
1100
1101# FIXME: implement proper completion and history
1102class CLI(cmd.Cmd):
1103 """The command-line interpreter."""
1104 @staticmethod
1105 def str2bool(s):
1106 """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
1107 return 1 if s in ["yes", "true", "on"] else 0
1108
1109 @staticmethod
1110 def bool2str(value):
1111 """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
1112 return "no" if value==0 else "yes"
1113
1114 @staticmethod
1115 def degree2pyuipc(degree):
1116 """Convert the given degree (as a string) into a PyUIPC value."""
1117 return int(float(degree) * 65536.0 * 65536.0 / 360.0)
1118
1119 @staticmethod
1120 def pyuipc2degree(value):
1121 """Convert the given PyUIPC value into a degree."""
1122 return value * 360.0 / 65536.0 / 65536.0
1123
1124 @staticmethod
1125 def fuelLevel2pyuipc(level):
1126 """Convert the given percentage value (as a string) into a PyUIPC value."""
1127 return int(float(level) * 128.0 * 65536.0 / 100.0)
1128
1129 @staticmethod
1130 def pyuipc2fuelLevel(value):
1131 """Convert the PyUIPC value into a percentage value."""
1132 return value * 100.0 / 128.0 / 65536.0
1133
1134 @staticmethod
1135 def fuelCapacity2pyuipc(capacity):
1136 """Convert the given capacity value (as a string) into a PyUIPC value."""
1137 return int(capacity)
1138
1139 @staticmethod
1140 def pyuipc2fuelCapacity(value):
1141 """Convert the given capacity value into a PyUIPC value."""
1142 return value
1143
1144 @staticmethod
1145 def throttle2pyuipc(throttle):
1146 """Convert the given throttle value (as a string) into a PyUIPC value."""
1147 return int(float(throttle) * 16384.0 / 100.0)
1148
1149 @staticmethod
1150 def pyuipc2throttle(value):
1151 """Convert the given PyUIPC value into a throttle value."""
1152 return value * 100.0 / 16384.0
1153
1154 @staticmethod
1155 def heading2pyuipc(heading):
1156 """Convert the given heading (as a string) into a PyUIPC value."""
1157 return int(float(heading) * 65536.0 / 360.0)
1158
1159 @staticmethod
1160 def pyuipc2heading(value):
1161 """Convert the given PyUIPC value into a heading."""
1162 return value * 360.0 / 65536.0
1163
1164 @staticmethod
1165 def altitude2pyuipc(altitude):
1166 """Convert the given altitude (as a string) into a PyUIPC value."""
1167 return int(float(altitude) * const.FEETTOMETRES * 65536.0)
1168
1169 @staticmethod
1170 def pyuipc2altitude(value):
1171 """Convert the given PyUIPC value into an altitude."""
1172 return value / const.FEETTOMETRES / 65536.0
1173
1174 def __init__(self):
1175 """Construct the CLI."""
1176 cmd.Cmd.__init__(self)
1177
1178 self.use_rawinput = True
1179 self.intro = "\nPyUIPC simulator command prompt\n"
1180 self.prompt = "PyUIPC> "
1181
1182 self.daemon = True
1183
1184 host = sys.argv[1] if len(sys.argv)>1 else "localhost"
1185 self._client = Client(host)
1186
1187 self._valueHandlers = {}
1188 self._valueHandlers["year"] = ([(0x0240, "H")], lambda value: value,
1189 lambda word: int(word))
1190 self._valueHandlers["yday"] = ([(0x023e, "H")], lambda value: value,
1191 lambda word: int(word))
1192 self._valueHandlers["hour"] = ([(0x023b, "b")], lambda value: value,
1193 lambda word: int(word))
1194 self._valueHandlers["min"] = ([(0x023c, "b")], lambda value: value,
1195 lambda word: int(word))
1196 self._valueHandlers["sec"] = ([(0x023a, "b")], lambda value: value,
1197 lambda word: int(word))
1198 self._valueHandlers["acftName"] = ([(0x3d00, -256)], lambda value: value,
1199 lambda word: word)
1200 self._valueHandlers["airPath"] = ([(0x3c00, -256)], lambda value: value,
1201 lambda word: word)
1202 self._valueHandlers["latitude"] = ([(0x0560, "l")],
1203 lambda value: value * 90.0 /
1204 10001750.0 / 65536.0 / 65536.0,
1205 lambda word: long(float(word) *
1206 10001750.0 *
1207 65536.0 * 65536.0 / 90.0))
1208 self._valueHandlers["longitude"] = ([(0x0568, "l")],
1209 lambda value: value * 360.0 /
1210 65536.0 / 65536.0 / 65536.0 / 65536.0,
1211 lambda word: long(float(word) *
1212 65536.0 * 65536.0 *
1213 65536.0 * 65536.0 /
1214 360.0))
1215 self._valueHandlers["paused"] = ([(0x0264, "H")], CLI.bool2str, CLI.str2bool)
1216 self._valueHandlers["frozen"] = ([(0x3364, "H")], CLI.bool2str, CLI.str2bool)
1217 self._valueHandlers["replay"] = ([(0x0628, "d")], CLI.bool2str, CLI.str2bool)
1218 self._valueHandlers["slew"] = ([(0x05dc, "H")], CLI.bool2str, CLI.str2bool)
1219 self._valueHandlers["overspeed"] = ([(0x036d, "b")], CLI.bool2str, CLI.str2bool)
1220 self._valueHandlers["stalled"] = ([(0x036c, "b")], CLI.bool2str, CLI.str2bool)
1221 self._valueHandlers["onTheGround"] = ([(0x0366, "H")], CLI.bool2str, CLI.str2bool)
1222 self._valueHandlers["zfw"] = ([(0x3bfc, "d")],
1223 lambda value: value * const.LBSTOKG / 256.0,
1224 lambda word: int(float(word) * 256.0 *
1225 const.KGSTOLB))
1226 self._valueHandlers["grossWeight"] = ([(0x30c0, "f")],
1227 lambda value: value * const.LBSTOKG,
1228 lambda word: None)
1229 self._valueHandlers["heading"] = ([(0x0580, "d")],
1230 CLI.pyuipc2degree, CLI.degree2pyuipc)
1231 self._valueHandlers["pitch"] = ([(0x0578, "d")],
1232 CLI.pyuipc2degree, CLI.degree2pyuipc)
1233 self._valueHandlers["bank"] = ([(0x057c, "d")],
1234 CLI.pyuipc2degree, CLI.degree2pyuipc)
1235 self._valueHandlers["ias"] = ([(0x02bc, "d")],
1236 lambda value: value / 128.0,
1237 lambda word: int(float(word) * 128.0))
1238 self._valueHandlers["mach"] = ([(0x11c6, "H")],
1239 lambda value: value / 20480.0,
1240 lambda word: int(float(word) * 20480.0))
1241 self._valueHandlers["gs"] = ([(0x02b4, "d")],
1242 lambda value: value * 3600.0 / 65536.0 / 1852.0,
1243 lambda word: int(float(word) * 65536.0 *
1244 1852.0 / 3600))
1245 self._valueHandlers["tas"] = ([(0x02b8, "d")],
1246 lambda value: value / 128.0,
1247 lambda word: None)
1248 self._valueHandlers["vs"] = ([(0x02c8, "d")],
1249 lambda value: value * 60 /
1250 const.FEETTOMETRES / 256.0,
1251 lambda word: int(float(word) *
1252 const.FEETTOMETRES *
1253 256.0 / 60.0))
1254 self._valueHandlers["tdRate"] = ([(0x030c, "d")],
1255 lambda value: value * 60 /
1256 const.FEETTOMETRES / 256.0,
1257 lambda word: int(float(word) *
1258 const.FEETTOMETRES *
1259 256.0 / 60.0))
1260 self._valueHandlers["radioAltitude"] = ([(0x31e4, "d")],
1261 lambda value: value /
1262 const.FEETTOMETRES /
1263 65536.0,
1264 lambda word: int(float(word) *
1265 const.FEETTOMETRES *
1266 65536.0))
1267 self._valueHandlers["altitude"] = ([(0x0570, "l")],
1268 lambda value: value /
1269 const.FEETTOMETRES / 65536.0 /
1270 65536.0,
1271 lambda word: long(float(word) *
1272 const.FEETTOMETRES *
1273 65536.0 * 65536.0))
1274 self._valueHandlers["gLoad"] = ([(0x11ba, "H")],
1275 lambda value: value / 625.0,
1276 lambda word: int(float(word) * 625.0))
1277
1278 self._valueHandlers["flapsControl"] = ([(0x0bdc, "d")],
1279 lambda value: value * 100.0 / 16383.0,
1280 lambda word: int(float(word) *
1281 16383.0 / 100.0))
1282 self._valueHandlers["flaps"] = ([(0x0be0, "d")],
1283 lambda value: value * 100.0 / 16383.0,
1284 lambda word: int(float(word) *
1285 16383.0 / 100.0))
1286 self._valueHandlers["lights"] = ([(0x0d0c, "H")],
1287 lambda value: value,
1288 lambda word: int(word))
1289 self._valueHandlers["pitot"] = ([(0x029c, "b")], CLI.bool2str, CLI.str2bool)
1290 self._valueHandlers["parking"] = ([(0x0bc8, "H")], CLI.bool2str, CLI.str2bool)
1291 self._valueHandlers["gearControl"] = ([(0x0be8, "d")],
1292 lambda value: value * 100.0 / 16383.0,
1293 lambda word: int(float(word) *
1294 16383.0 / 100.0))
1295 self._valueHandlers["noseGear"] = ([(0x0bec, "d")],
1296 lambda value: value * 100.0 / 16383.0,
1297 lambda word: int(float(word) *
1298 16383.0 / 100.0))
1299 self._valueHandlers["spoilersArmed"] = ([(0x0bcc, "d")],
1300 CLI.bool2str, CLI.str2bool)
1301 self._valueHandlers["spoilers"] = ([(0x0bd0, "d")],
1302 lambda value: value,
1303 lambda word: int(word))
1304 self._valueHandlers["altimeter"] = ([(0x0330, "H")],
1305 lambda value: value / 16.0,
1306 lambda word: int(float(word)*16.0))
1307 self._valueHandlers["qnh"] = ([(0x0ec6, "H")],
1308 lambda value: value / 16.0,
1309 lambda word: int(float(word)*16.0))
1310 self._valueHandlers["nav1"] = ([(0x0350, "H")],
1311 Values._writeFrequency,
1312 lambda word: Values._readFrequency(float(word)))
1313 self._valueHandlers["nav1_obs"] = ([(0x0c4e, "H")],
1314 lambda value: value,
1315 lambda word: int(word))
1316 self._valueHandlers["nav2"] = ([(0x0352, "H")],
1317 Values._writeFrequency,
1318 lambda word: Values._readFrequency(float(word)))
1319 self._valueHandlers["nav2_obs"] = ([(0x0c5e, "H")],
1320 lambda value: value,
1321 lambda word: int(word))
1322 self._valueHandlers["adf1"] = ([(0x034c, "H"), (0x0356, "H")],
1323 lambda values:
1324 Values._toADFFrequency(values[0],
1325 values[1]),
1326 lambda word:
1327 Values._readADFFrequency(float(word)))
1328 self._valueHandlers["adf2"] = ([(0x02d4, "H"), (0x02d6, "H")],
1329 lambda values:
1330 Values._toADFFrequency(values[0],
1331 values[1]),
1332 lambda word:
1333 Values._readADFFrequency(float(word)))
1334 self._valueHandlers["squawk"] = ([(0x0354, "H")],
1335 Values._writeBCD,
1336 lambda word: Values._readBCD(int(word)))
1337 self._valueHandlers["windSpeed"] = ([(0x0e90, "H")],
1338 lambda value: value,
1339 lambda word: int(word))
1340 self._valueHandlers["windDirection"] = ([(0x0e92, "H")],
1341 lambda value: value * 360.0 / 65536.0,
1342 lambda word: int(int(word) *
1343 65536.0 / 360.0))
1344 self._valueHandlers["fuelWeight"] = ([(0x0af4, "H")],
1345 lambda value: value / 256.0,
1346 lambda word: int(float(word)*256.0))
1347
1348
1349 self._valueHandlers["centreLevel"] = ([(0x0b74, "d")],
1350 CLI.pyuipc2fuelLevel,
1351 CLI.fuelLevel2pyuipc)
1352 self._valueHandlers["centreCapacity"] = ([(0x0b78, "d")],
1353 CLI.pyuipc2fuelCapacity,
1354 CLI.fuelCapacity2pyuipc)
1355 self._valueHandlers["leftMainLevel"] = ([(0x0b7c, "d")],
1356 CLI.pyuipc2fuelLevel,
1357 CLI.fuelLevel2pyuipc)
1358 self._valueHandlers["leftMainCapacity"] = ([(0x0b80, "d")],
1359 CLI.pyuipc2fuelCapacity,
1360 CLI.fuelCapacity2pyuipc)
1361 self._valueHandlers["leftAuxLevel"] = ([(0x0b84, "d")],
1362 CLI.pyuipc2fuelLevel,
1363 CLI.fuelLevel2pyuipc)
1364 self._valueHandlers["leftAuxCapacity"] = ([(0x0b88, "d")],
1365 CLI.pyuipc2fuelCapacity,
1366 CLI.fuelCapacity2pyuipc)
1367 self._valueHandlers["leftTipLevel"] = ([(0x0b8c, "d")],
1368 CLI.pyuipc2fuelLevel,
1369 CLI.fuelLevel2pyuipc)
1370 self._valueHandlers["leftTipCapacity"] = ([(0x0b90, "d")],
1371 CLI.pyuipc2fuelCapacity,
1372 CLI.fuelCapacity2pyuipc)
1373 self._valueHandlers["rightMainLevel"] = ([(0x0b94, "d")],
1374 CLI.pyuipc2fuelLevel,
1375 CLI.fuelLevel2pyuipc)
1376 self._valueHandlers["rightMainCapacity"] = ([(0x0b98, "d")],
1377 CLI.pyuipc2fuelCapacity,
1378 CLI.fuelCapacity2pyuipc)
1379 self._valueHandlers["rightAuxLevel"] = ([(0x0b9c, "d")], CLI.pyuipc2fuelLevel,
1380 CLI.fuelLevel2pyuipc)
1381 self._valueHandlers["rightAuxCapacity"] = ([(0x0ba0, "d")],
1382 CLI.pyuipc2fuelCapacity,
1383 CLI.fuelCapacity2pyuipc)
1384 self._valueHandlers["rightTipLevel"] = ([(0x0ba4, "d")],
1385 CLI.pyuipc2fuelLevel,
1386 CLI.fuelLevel2pyuipc)
1387 self._valueHandlers["rightTipCapacity"] = ([(0x0ba8, "d")],
1388 CLI.pyuipc2fuelCapacity,
1389 CLI.fuelCapacity2pyuipc)
1390 self._valueHandlers["centre2Level"] = ([(0x1244, "d")],
1391 CLI.pyuipc2fuelLevel,
1392 CLI.fuelLevel2pyuipc)
1393 self._valueHandlers["centre2Capacity"] = ([(0x1248, "d")],
1394 CLI.pyuipc2fuelCapacity,
1395 CLI.fuelCapacity2pyuipc)
1396 self._valueHandlers["external1Level"] = ([(0x1254, "d")],
1397 CLI.pyuipc2fuelLevel,
1398 CLI.fuelLevel2pyuipc)
1399 self._valueHandlers["external1Capacity"] = ([(0x1258, "d")],
1400 CLI.pyuipc2fuelCapacity,
1401 CLI.fuelCapacity2pyuipc)
1402 self._valueHandlers["external2Level"] = ([(0x125c, "d")],
1403 CLI.pyuipc2fuelLevel,
1404 CLI.fuelLevel2pyuipc)
1405 self._valueHandlers["external2Capacity"] = ([(0x1260, "d")],
1406 CLI.pyuipc2fuelCapacity,
1407 CLI.fuelCapacity2pyuipc)
1408
1409 self._valueHandlers["n1_1"] = ([(0x2000, "f")], lambda value: value,
1410 lambda word: float(word))
1411 self._valueHandlers["n1_2"] = ([(0x2100, "f")], lambda value: value,
1412 lambda word: float(word))
1413 self._valueHandlers["n1_3"] = ([(0x2200, "f")], lambda value: value,
1414 lambda word: float(word))
1415
1416 self._valueHandlers["throttle_1"] = ([(0x088c, "H")],
1417 CLI.pyuipc2throttle,
1418 CLI.throttle2pyuipc)
1419 self._valueHandlers["throttle_2"] = ([(0x0924, "H")],
1420 CLI.pyuipc2throttle,
1421 CLI.throttle2pyuipc)
1422 self._valueHandlers["throttle_3"] = ([(0x09bc, "H")],
1423 CLI.pyuipc2throttle,
1424 CLI.throttle2pyuipc)
1425
1426 self._valueHandlers["visibility"] = ([(0x0e8a, "H")],
1427 lambda value: value*1609.344/100.0,
1428 lambda word: int(float(word)*
1429 100.0/1609.344))
1430
1431 self._valueHandlers["payloadCount"] = ([(0x13fc, "d")],
1432 lambda value: value,
1433 lambda word: int(word))
1434 for i in range(0, 61):
1435 self._valueHandlers["payload%d" % (i,)] = ([(0x1400 + i * 48, "f")],
1436 lambda value:
1437 value * const.LBSTOKG,
1438 lambda word:
1439 float(word)*const.KGSTOLB)
1440 self._valueHandlers["textScrolling"] = ([(0x1274, "h")],
1441 CLI.bool2str, CLI.str2bool)
1442
1443 self._valueHandlers["messageDuration"] = ([(0x32fa, "h")],
1444 lambda value: value,
1445 lambda word: int(word))
1446 self._valueHandlers["message"] = ([(0x3380, -128)],
1447 lambda value: value,
1448 lambda word: word)
1449
1450 for i in range(0, Values.HOTKEY_SIZE):
1451 self._valueHandlers["hotkey%d" % (i,)] = ([(0x3210 + i*4, "u")],
1452 lambda value: "0x%08x" % (value,),
1453 lambda word: long(word, 16))
1454
1455 self._valueHandlers["cog"] = ([(0x2ef8, "f")], lambda value: value,
1456 lambda word: float(word))
1457
1458 self._valueHandlers["pmdg_737ng_switches"] = ([(0x6202, "b")],
1459 lambda value: value,
1460 lambda word: int(word))
1461
1462 self._valueHandlers["pmdg_737ngx_lts_positionsw"] = ([(0x6500, "b")],
1463 lambda value: value,
1464 lambda word: int(word))
1465 self._valueHandlers["xpdrC"] = ([(0x7b91, "b")],
1466 lambda value: value,
1467 lambda word: int(word))
1468
1469 self._valueHandlers["apMaster"] = ([(0x07bc, "u")],
1470 CLI.bool2str, CLI.str2bool)
1471 self._valueHandlers["apHeadingHold"] = ([(0x07c8, "u")],
1472 CLI.bool2str, CLI.str2bool)
1473 self._valueHandlers["apHeading"] = ([(0x07cc, "H")],
1474 CLI.pyuipc2heading,
1475 CLI.heading2pyuipc)
1476 self._valueHandlers["apAltitudeHold"] = ([(0x07d0, "u")],
1477 CLI.bool2str, CLI.str2bool)
1478 self._valueHandlers["apAltitude"] = ([(0x07d4, "H")],
1479 CLI.pyuipc2altitude,
1480 CLI.altitude2pyuipc)
1481
1482 self._valueHandlers["trim"] = ([(0x2ea0, "f")],
1483 lambda value: value * 180.0 / math.pi,
1484 lambda word:
1485 float(word) * math.pi / 180.0)
1486
1487 self._valueHandlers["eng1Deice"] = ([(0x08b2, "H")],
1488 CLI.bool2str, CLI.str2bool)
1489 self._valueHandlers["eng2Deice"] = ([(0x094a, "H")],
1490 CLI.bool2str, CLI.str2bool)
1491 self._valueHandlers["eng3Deice"] = ([(0x09e2, "H")],
1492 CLI.bool2str, CLI.str2bool)
1493 self._valueHandlers["propDeice"] = ([(0x337c, "b")],
1494 CLI.bool2str, CLI.str2bool)
1495 self._valueHandlers["structDeice"] = ([(0x337d, "b")],
1496 CLI.bool2str, CLI.str2bool)
1497
1498 def default(self, line):
1499 """Handle unhandle commands."""
1500 if line=="EOF":
1501 print
1502 return self.do_quit("")
1503 else:
1504 return super(CLI, self).default(line)
1505
1506 def do_get(self, args):
1507 """Handle the get command."""
1508 names = args.split()
1509 data = []
1510 for name in names:
1511 if name not in self._valueHandlers:
1512 print >> sys.stderr, "Unknown variable: " + name
1513 return False
1514 valueHandler = self._valueHandlers[name]
1515 data += valueHandler[0]
1516
1517 try:
1518 results = self._client.read(data)
1519 index = 0
1520 i = 0
1521 while index<len(results):
1522 name = names[i]
1523 valueHandler = self._valueHandlers[name]
1524 numResults = len(valueHandler[0])
1525 thisResults = results[index:index+numResults]
1526 value = valueHandler[1](thisResults[0] if numResults==1
1527 else thisResults)
1528
1529 print name + "=" + str(value)
1530
1531 index += numResults
1532 i+=1
1533 except Exception, e:
1534 print >> sys.stderr, "Failed to read data: " + str(e)
1535
1536 return False
1537
1538 def help_get(self):
1539 """Print help for the get command."""
1540 print "get <variable> [<variable>...]"
1541
1542 def complete_get(self, text, line, begidx, endidx):
1543 """Try to complete the get command."""
1544 return [key for key in self._valueHandlers if key.startswith(text)]
1545
1546 def do_set(self, args):
1547 """Handle the set command."""
1548 arguments = []
1549 inWord = False
1550 inQuote = False
1551 word = ""
1552 for c in args:
1553 if c.isspace() and not inQuote:
1554 if inWord:
1555 arguments.append(word)
1556 word = ""
1557 inWord = False
1558 elif c=='"':
1559 inQuote = not inQuote
1560 else:
1561 inWord = True
1562 word += c
1563
1564 if inWord:
1565 arguments.append(word)
1566
1567 names = []
1568 data = []
1569 for argument in arguments:
1570 words = argument.split("=")
1571 if len(words)!=2:
1572 print >> sys.stderr, "Invalid argument: " + argument
1573 return False
1574
1575 (name, value) = words
1576 if name not in self._valueHandlers:
1577 print >> sys.stderr, "Unknown variable: " + name
1578 return False
1579
1580 valueHandler = self._valueHandlers[name]
1581 try:
1582 values = valueHandler[2](value)
1583 if len(valueHandler[0])==1:
1584 values = [values]
1585 index = 0
1586 for (offset, type) in valueHandler[0]:
1587 data.append((offset, type, values[index]))
1588 index += 1
1589 except Exception, e:
1590 print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
1591 (value, name, str(e))
1592 return False
1593
1594 try:
1595 self._client.write(data)
1596 print "Data written"
1597 except Exception, e:
1598 print >> sys.stderr, "Failed to write data: " + str(e)
1599
1600 return False
1601
1602 def help_set(self):
1603 """Print help for the set command."""
1604 print "set <variable>=<value> [<variable>=<value>...]"
1605
1606 def complete_set(self, text, line, begidx, endidx):
1607 """Try to complete the set command."""
1608 if not text and begidx>0 and line[begidx-1]=="=":
1609 return []
1610 else:
1611 return [key + "=" for key in self._valueHandlers if key.startswith(text)]
1612
1613 def do_setversion(self, args):
1614 """Set the version number to simulate"""
1615 try:
1616 value = int(args)
1617 self._client.setVersion(value)
1618 print "Emulating version %d" % (value,)
1619 except Exception, e:
1620 print >> sys.stderr, "Failed to set the version: " + str(e)
1621
1622 def help_setversion(self, usage = False):
1623 """Help for the setversion command"""
1624 if usage: print "Usage:",
1625 print "setversion <number>"
1626
1627 def do_close(self, args):
1628 """Close an existing connection so that FS will fail."""
1629 try:
1630 self._client.close()
1631 print "Connection closed"
1632 except Exception, e:
1633 print >> sys.stderr, "Failed to close the connection: " + str(e)
1634
1635 def do_failopen(self, args):
1636 """Enable/disable the failing of opens."""
1637 try:
1638 value = self.str2bool(args)
1639 self._client.failOpen(value)
1640 print "Opening will%s fail" % ("" if value else " not",)
1641 except Exception, e:
1642 print >> sys.stderr, "Failed to set open failure: " + str(e)
1643
1644 def help_failopen(self, usage = False):
1645 """Help for the failopen command"""
1646 if usage: print "Usage:",
1647 print "failopen yes|no"
1648
1649 def complete_failopen(self, text, line, begidx, endidx):
1650 if text:
1651 if "yes".startswith(text): return ["yes"]
1652 elif "no".startswith(text): return ["no"]
1653 else: return []
1654 else:
1655 return ["yes", "no"]
1656
1657 def do_quit(self, args):
1658 """Handle the quit command."""
1659 self._client.quit()
1660 return True
1661
1662#------------------------------------------------------------------------------
1663
1664if __name__ == "__main__":
1665 CLI().cmdloop()
1666else:
1667 server = Server()
1668 server.start()
1669
1670#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.