source: src/mlx/pyuipc_sim.py@ 334:7978a2496c31

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

#139: the transponder state is now queried and made available

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