source: src/mlx/pyuipc_sim.py@ 1085:31eec9ef6a2d

python3
Last change on this file since 1085:31eec9ef6a2d was 1077:e230d26ca700, checked in by István Váradi <ivaradi@…>, 22 months ago

Constants and basic support for MSFS 2020 (re #364).

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