source: src/mlx/pyuipc_sim.py@ 353:f3c95dc2eaea

Last change on this file since 353:f3c95dc2eaea was 343:d13af9285d29, checked in by István Váradi <ivaradi@…>, 12 years ago

Added support for the elevator trim

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