source: src/mlx/pyuipc_sim.py@ 485:b254e5cd222e

Last change on this file since 485:b254e5cd222e was 443:acbe605bb814, checked in by István Váradi <ivaradi@…>, 12 years ago

Added support for the BAe 146-200 aircraft type (re #182)

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