source: src/mlx/pyuipc_sim.py@ 603:6cf5eff2a6d7

Last change on this file since 603:6cf5eff2a6d7 was 559:54fa2efc1dc2, checked in by István Váradi <ivaradi@…>, 11 years ago

Added debug printout to see what is read from FS when the flaps settings change (re #225)

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