source: src/mlx/pyuipc_sim.py@ 1097:f2ac841cffec

python3
Last change on this file since 1097:f2ac841cffec was 1089:03726a21f686, checked in by István Váradi <ivaradi@…>, 21 months ago

FSUIPC offset 0x0590 is used to query altitude on MSFS 2020 (re #366)

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