source: src/mlx/pyuipc_sim.py@ 213:206190c8a76e

Last change on this file since 213:206190c8a76e was 213:206190c8a76e, checked in by István Váradi <ivaradi@…>, 12 years ago

Fixed some minor problems with the PyUIPC simulator

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