source: src/mlx/pyuipc_sim.py@ 313:8a6711d8fbcc

Last change on this file since 313:8a6711d8fbcc was 298:24c67ec5cdca, checked in by István Váradi <ivaradi@…>, 12 years ago

Documented the non-GUI modules

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