source: src/mlx/pyuipc_sim.py@ 292:8528c186485c

Last change on this file since 292:8528c186485c was 273:066f7271e849, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the playback of approach callouts

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