source: src/mlx/pyuipc_sim.py@ 331:79755718ab7d

Last change on this file since 331:79755718ab7d was 321:8f6b55fb98ed, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the forced logging of radio frequencies when entering certain stages of the flight

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