source: src/mlx/pyuipc_sim.py@ 317:26cd9b16bf92

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

Added the logging of the ADF frequencies

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