source: src/mlx/pyuipc_sim.py@ 341:c18840deae77

Last change on this file since 341:c18840deae77 was 336:2fa4d33cd52b, checked in by István Váradi <ivaradi@…>, 12 years ago

#138: added support for the AP-related offsets in the simulator

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