source: src/mlx/pyuipc_sim.py@ 1013:84d79c1cab91

python3
Last change on this file since 1013:84d79c1cab91 was 969:1e193fa64d1e, checked in by István Váradi <ivaradi@…>, 6 years ago

Strings are returned as string by the FSUIPC simulator (re #347)

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