source: src/mlx/pyuipc_sim.py

python3
Last change on this file was 1149:2bab29b55a4e, checked in by István Váradi <ivaradi@…>, 44 hours ago

Support for offset 0x11b8 in the FSUIPC simulator

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