source: src/mlx/pyuipc_sim.py@ 402:b668511b4ac8

Last change on this file since 402:b668511b4ac8 was 390:7378fc230e10, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the handling of the anti-ice values and extended the simulator to handle them too (re #159)

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