source: src/mlx/pyuipc_sim.py@ 803:1ed515fbcfdc

Last change on this file since 803:1ed515fbcfdc was 792:b2e45d10d283, checked in by István Váradi <ivaradi@…>, 8 years ago

Added error exception simulating real life (re #303)

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