source: src/mlx/pyuipc_sim.py@ 731:0290e3f39968

Last change on this file since 731:0290e3f39968 was 658:e97b6a3f4121, checked in by István Váradi <ivaradi@…>, 9 years ago

Added the version constant for P3D to the PyUIPC simulator (re #273)

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