source: src/mlx/pyuipc_sim.py@ 56:1b8b546fd929

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

Added the fuel, N1 and throttle values to the CLI as well.

File size: 45.9 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
14
15#------------------------------------------------------------------------------
16
17# Version constants
18SIM_ANY=0
19SIM_FS98=1
20SIM_FS2K=2
21SIM_CFS2=3
22SIM_CFS1=4
23SIM_FLY=5
24SIM_FS2K2=6
25SIM_FS2K4=7
26
27#------------------------------------------------------------------------------
28
29# Error constants
30ERR_OK=0
31ERR_OPEN=1
32ERR_NOFS=2
33ERR_REGMSG=3
34ERR_ATOM=4
35ERR_MAP=5
36ERR_VIEW=6
37ERR_VERSION=7
38ERR_WRONGFS=8
39ERR_NOTOPEN=9
40ERR_NODATA=10
41ERR_TIMEOUT=11
42ERR_SENDMSG=12
43ERR_DATA=13
44ERR_RUNNING=14
45ERR_SIZE=15
46
47#------------------------------------------------------------------------------
48
49# The version of FSUIPC
50fsuipc_version=0x0401
51lib_version=0x0302
52fs_version=SIM_FS2K4
53
54#------------------------------------------------------------------------------
55
56class FSUIPCException(Exception):
57 """FSUIPC exception class.
58
59 It contains a member variable named errorCode. The string is a text
60 describing the error."""
61
62 errors=["OK",
63 "Attempt to Open when already Open",
64 "Cannot link to FSUIPC or WideClient",
65 "Failed to Register common message with Windows",
66 "Failed to create Atom for mapping filename",
67 "Failed to create a file mapping object",
68 "Failed to open a view to the file map",
69 "Incorrect version of FSUIPC, or not FSUIPC",
70 "Sim is not version requested",
71 "Call cannot execute, link not Open",
72 "Call cannot execute: no requests accumulated",
73 "IPC timed out all retries",
74 "IPC sendmessage failed all retries",
75 "IPC request contains bad data",
76 "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
77 "Read or Write request cannot be added, memory for Process is full"]
78
79 def __init__(self, errorCode):
80 """
81 Construct the exception
82 """
83 if errorCode<len(self.errors):
84 self.errorString = self.errors[errorCode]
85 else:
86 self.errorString = "Unknown error"
87 Exception.__init__(self, self.errorString)
88 self.errorCode = errorCode
89
90 def __str__(self):
91 """
92 Convert the excption to string
93 """
94 return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
95
96#------------------------------------------------------------------------------
97
98class Values(object):
99 """The values that can be read from 'FSUIPC'."""
100 # Fuel data index: centre tank
101 FUEL_CENTRE = 0
102
103 # Fuel data index: left main tank
104 FUEL_LEFT = 1
105
106 # Fuel data index: right main tank
107 FUEL_RIGHT = 2
108
109 # Fuel data index: left aux tank
110 FUEL_LEFT_AUX = 3
111
112 # Fuel data index: right aux tank
113 FUEL_RIGHT_AUX = 4
114
115 # Fuel data index: left tip tank
116 FUEL_LEFT_TIP = 5
117
118 # Fuel data index: right tip tank
119 FUEL_RIGHT_AUX = 6
120
121 # Fuel data index: external 1 tank
122 FUEL_EXTERNAL_1 = 7
123
124 # Fuel data index: external 2 tank
125 FUEL_EXTERNAL_2 = 8
126
127 # Fuel data index: centre 2 tank
128 FUEL_CENTRE_2 = 9
129
130 # The number of fuel tank entries
131 NUM_FUEL = FUEL_CENTRE_2 + 1
132
133 # Engine index: engine #1
134 ENGINE_1 = 0
135
136 # Engine index: engine #2
137 ENGINE_2 = 1
138
139 # Engine index: engine #3
140 ENGINE_3 = 2
141
142 @staticmethod
143 def _readFrequency(frequency):
144 """Convert the given frequency into BCD."""
145 return Values._readBCD(int(frequency*100.0))
146
147 @staticmethod
148 def _readBCD(value):
149 """Convert the given value into BCD format."""
150 bcd = (value/1000) % 10
151 bcd <<= 4
152 bcd |= (value/100) % 10
153 bcd <<= 4
154 bcd |= (value/10) % 10
155 bcd <<= 4
156 bcd |= value % 10
157 return bcd
158
159 @staticmethod
160 def _writeFrequency(value):
161 """Convert the given value into a frequency."""
162 return (Values._writeBCD(value) + 10000) / 100.0
163
164 @staticmethod
165 def _writeBCD(value):
166 """Convert the given BCD value into a real value."""
167 bcd = (value>>12) & 0x0f
168 bcd *= 10
169 bcd += (value>>8) & 0x0f
170 bcd *= 10
171 bcd += (value>>4) & 0x0f
172 bcd *= 10
173 bcd += (value>>0) & 0x0f
174
175 return bcd
176
177 def __init__(self):
178 """Construct the values with defaults."""
179 self._timeOffset = 0
180 self.airPath = "C:\\Program Files\\Microsoft Games\\" \
181 "FS9\\Aircraft\\Cessna\\cessna172.air"
182 self.aircraftName = "Cessna 172SP"
183 self.flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40]
184 self.fuelCapacities = [10000.0, 5000.0, 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
185
186 self.paused = False
187 self.frozen = False
188 self.replay = False
189 self.slew = False
190 self.overspeed = False
191 self.stalled = False
192 self.onTheGround = True
193
194 self.zfw = 50000.0
195
196 self.fuelWeights = [0.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
197 # Wikipedia: "Jet fuel", Jet A-1 density at 15*C .804kg/l -> 6.7 pounds/gallon
198 self.fuelWeight = 6.70970518
199
200 self.heading = 220.0
201 self.pitch = 0.0
202 self.bank = 0.0
203
204 self.ias = 0.0
205 self.vs = 0.0
206
207 self.radioAltitude = None
208 self.altitude = 513.0
209
210 self.gLoad = 1.0
211
212 self.flapsControl = 0.0
213 self.flaps = 0.0
214
215 self.navLightsOn = True
216 self.antiCollisionLightsOn = False
217 self.landingLightsOn = False
218 self.strobeLightsOn = False
219
220 self.pitot = False
221 self.parking = True
222
223 self.noseGear = 1.0
224
225 self.spoilersArmed = False
226 self.spoilers = 0.0
227
228 self.altimeter = 1013.0
229
230 self.nav1 = 117.3
231 self.nav2 = 109.5
232 self.squawk = 2200
233
234 self.windSpeed = 8.0
235 self.windDirection = 300.0
236
237 self.n1 = [0.0, 0.0, 0.0]
238 self.throttles = [0.0, 0.0, 0.0]
239
240 def read(self, offset):
241 """Read the value at the given offset."""
242 try:
243 return self._read(offset)
244 except Exception, e:
245 print "failed to read offset %04x: %s" % (offset, str(e))
246 raise FSUIPCException(ERR_DATA)
247
248 def _read(self, offset):
249 """Read the value at the given offset."""
250 if offset==0x023a: # Second of time
251 return self._readUTC().tm_sec
252 elif offset==0x023b: # Hour of Zulu time
253 return self._readUTC().tm_hour
254 elif offset==0x023c: # Minute of Zulu time
255 return self._readUTC().tm_min
256 elif offset==0x023e: # Day number on year
257 return self._readUTC().tm_yday
258 elif offset==0x0240: # Year in FS
259 return self._readUTC().tm_year
260 elif offset==0x0264: # Paused
261 return 1 if self.paused else 0
262 elif offset==0x029c: # Pitot
263 return 1 if self.pitot else 0
264 elif offset==0x02b4: # Ground speed
265 # FIXME: calculate TAS first, then from the heading and
266 # wind the GS
267 return int(self.ias * 65536.0 * 1852.0 / 3600.0)
268 elif offset==0x02bc: # IAS
269 return int(self.ias * 128.0)
270 elif offset==0x02c8: # VS
271 return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
272 elif offset==0x0330: # Altimeter
273 return int(self.altimeter * 16.0)
274 elif offset==0x0350: # NAV1
275 return Values._readFrequency(self.nav1)
276 elif offset==0x0352: # NAV2
277 return Values._readFrequency(self.nav2)
278 elif offset==0x0354: # Squawk
279 return Values._readBCD(self.squawk)
280 elif offset==0x0366: # On the ground
281 return 1 if self.onTheGround else 0
282 elif offset==0x036c: # Stalled
283 return 1 if self.stalled else 0
284 elif offset==0x036d: # Overspeed
285 return 1 if self.overspeed else 0
286 elif offset==0x0570: # Altitude
287 return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
288 elif offset==0x0578: # Pitch
289 return int(self.pitch * 65536.0 * 65536.0 / 360.0)
290 elif offset==0x057c: # Bank
291 return int(self.bank * 65536.0 * 65536.0 / 360.0)
292 elif offset==0x0580: # Heading
293 return int(self.heading * 65536.0 * 65536.0 / 360.0)
294 elif offset==0x05dc: # Slew
295 return 1 if self.slew else 0
296 elif offset==0x0628: # Replay
297 return 1 if self.replay else 0
298 elif offset==0x088c: # Engine #1 throttle
299 return self._getThrottle(self.ENGINE_1)
300 elif offset==0x0924: # Engine #2 throttle
301 return self._getThrottle(self.ENGINE_2)
302 elif offset==0x09bc: # Engine #3 throttle
303 return self._getThrottle(self.ENGINE_3)
304 elif offset==0x0af4: # Fuel weight
305 return int(self.fuelWeight * 256.0)
306 elif offset==0x0b74: # Centre tank level
307 return self._getFuelLevel(self.FUEL_CENTRE)
308 elif offset==0x0b78: # Centre tank capacity
309 return self._getFuelCapacity(self.FUEL_CENTRE)
310 elif offset==0x0b7c: # Left tank level
311 return self._getFuelLevel(self.FUEL_LEFT)
312 elif offset==0x0b80: # Left tank capacity
313 return self._getFuelCapacity(self.FUEL_LEFT)
314 elif offset==0x0b84: # Left aux tank level
315 return self._getFuelLevel(self.FUEL_LEFT_AUX)
316 elif offset==0x0b88: # Left aux tank capacity
317 return self._getFuelCapacity(self.FUEL_LEFT_AUX)
318 elif offset==0x0b8c: # Left tip tank level
319 return self._getFuelLevel(self.FUEL_LEFT_TIP)
320 elif offset==0x0b90: # Left tip tank capacity
321 return self._getFuelCapacity(self.FUEL_LEFT_TIP)
322 elif offset==0x0b94: # Right aux tank level
323 return self._getFuelLevel(self.FUEL_RIGHT)
324 elif offset==0x0b98: # Right aux tank capacity
325 return self._getFuelCapacity(self.FUEL_RIGHT)
326 elif offset==0x0b9c: # Right tank level
327 return self._getFuelLevel(self.FUEL_RIGHT_AUX)
328 elif offset==0x0ba0: # Right tank capacity
329 return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
330 elif offset==0x0ba4: # Right tip tank level
331 return self._getFuelLevel(self.FUEL_RIGHT_TIP)
332 elif offset==0x0ba8: # Right tip tank capacity
333 return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
334 elif offset==0x0bc8: # Parking
335 return 1 if self.parking else 0
336 elif offset==0x0bcc: # Spoilers armed
337 return 1 if self.spoilersArmed else 0
338 elif offset==0x0bd0: # Spoilers
339 return 0 if self.spoilers == 0 \
340 else int(self.spoilers * (16383 - 4800) + 4800)
341 elif offset==0x0bdc: # Flaps control
342 numNotchesM1 = len(self.flapsNotches) - 1
343 flapsIncrement = 16383.0 / numNotchesM1
344 index = 0
345 while index<numNotchesM1 and \
346 self.flapsControl<self.flapsNotches[index]:
347 index += 1
348
349 if index==numNotchesM1:
350 return 16383
351 else:
352 return int((self.flapsControl-self.flapsNotches[index]) * \
353 flapsIncrement / \
354 (self.flapsNotches[index+1] - self.flapsNotches[index]))
355 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
356 return self.flaps * 16383.0 / self.flapsNotches[-1]
357 elif offset==0x0bec: # Nose gear
358 return int(self.noseGear * 16383.0)
359 elif offset==0x0d0c: # Lights
360 lights = 0
361 if self.navLightsOn: lights |= 0x01
362 if self.antiCollisionLightsOn: lights |= 0x02
363 if self.landingLightsOn: lights |= 0x04
364 if self.strobeLightsOn: lights |= 0x10
365 return lights
366 elif offset==0x0e90: # Wind speed
367 return int(self.windSpeed)
368 elif offset==0x0e92: # Wind direction
369 return int(self.windDirection * 65536.0 / 360.0)
370 elif offset==0x11ba: # G-Load
371 return int(self.gLoad * 625.0)
372 elif offset==0x11c6: # Mach
373 # FIXME: calculate from IAS, altitude and QNH
374 return int(self.ias * 0.05 * 20480.)
375 elif offset==0x1244: # Centre 2 tank level
376 return self._getFuelLevel(self.FUEL_CENTRE_2)
377 elif offset==0x1248: # Centre 2 tank capacity
378 return self._getFuelCapacity(self.FUEL_CENTRE_2)
379 elif offset==0x1254: # External 1 tank level
380 return self._getFuelLevel(self.FUEL_EXTERNAL_1)
381 elif offset==0x1258: # External 1 tank capacity
382 return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
383 elif offset==0x125c: # External 2 tank level
384 return self._getFuelLevel(self.FUEL_EXTERNAL_2)
385 elif offset==0x1260: # External 2 tank capacity
386 return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
387 elif offset==0x2000: # Engine #1 N1
388 return self.n1[self.ENGINE_1]
389 elif offset==0x2100: # Engine #2 N1
390 return self.n1[self.ENGINE_2]
391 elif offset==0x2200: # Engine #3 N1
392 return self.n1[self.ENGINE_3]
393 elif offset==0x30c0: # Gross weight
394 return (self.zfw + sum(self.fuelWeights)) * const.KGSTOLB
395 elif offset==0x31e4: # Radio altitude
396 # FIXME: if self.radioAltitude is None, calculate from the
397 # altitude with some, perhaps random, ground altitude
398 # value
399 radioAltitude = (self.altitude - 517) \
400 if self.radioAltitude is None else self.radioAltitude
401 return (radioAltitude * const.FEETTOMETRES * 65536.0)
402 elif offset==0x3364: # Frozen
403 return 1 if self.frozen else 0
404 elif offset==0x3bfc: # ZFW
405 return int(self.zfw * 256.0 * const.KGSTOLB)
406 elif offset==0x3c00: # Path of the current AIR file
407 return self.airPath
408 elif offset==0x3d00: # Name of the current aircraft
409 return self.aircraftName
410 else:
411 print "Unhandled offset: %04x" % (offset,)
412 raise FSUIPCException(ERR_DATA)
413
414 def write(self, offset, value):
415 """Write the value at the given offset."""
416 try:
417 return self._write(offset, value)
418 except Exception, e:
419 print "failed to write offset %04x: %s" % (offset, str(e))
420 raise FSUIPCException(ERR_DATA)
421
422 def _write(self, offset, value):
423 """Write the given value at the given offset."""
424 if offset==0x023a: # Second of time
425 self._updateTimeOffset(5, value)
426 elif offset==0x023b: # Hour of Zulu time
427 self._updateTimeOffset(3, value)
428 elif offset==0x023c: # Minute of Zulu time
429 self._updateTimeOffset(4, value)
430 elif offset==0x023e: # Day number in the year
431 self._updateTimeOffset(7, value)
432 elif offset==0x0240: # Year in FS
433 self._updateTimeOffset(0, value)
434 elif offset==0x0264: # Paused
435 self.paused = value!=0
436 elif offset==0x029c: # Pitot
437 self.pitot = value!=0
438 elif offset==0x02b4: # Ground speed
439 # FIXME: calculate TAS using the heading and the wind, and
440 # then IAS based on the altitude
441 self.ias = value * 3600.0 / 65536.0 / 1852.0
442 elif offset==0x02bc: # IAS
443 self.ias = value / 128.0
444 elif offset==0x02c8: # VS
445 self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
446 elif offset==0x0330: # Altimeter
447 self.altimeter = value / 16.0
448 elif offset==0x0350: # NAV1
449 self.nav1 = Values._writeFrequency(value)
450 elif offset==0x0352: # NAV2
451 self.nav2 = Values._writeFrequency(value)
452 elif offset==0x0354: # Squawk
453 self.squawk = Values._writeBCD(value)
454 elif offset==0x0366: # Stalled
455 self.onTheGround = value!=0
456 elif offset==0x036c: # Stalled
457 self.stalled = value!=0
458 elif offset==0x036d: # Overspeed
459 self.overspeed = value!=0
460 elif offset==0x0570: # Altitude
461 self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
462 elif offset==0x0578: # Pitch
463 self.pitch = value * 360.0 / 65536.0 / 65536.0
464 elif offset==0x057c: # Bank
465 self.bank = value * 360.0 / 65536.0 / 65536.0
466 elif offset==0x0580: # Heading
467 self.heading = value * 360.0 / 65536.0 / 65536.0
468 elif offset==0x05dc: # Slew
469 self.slew = value!=0
470 elif offset==0x0628: # Replay
471 self.replay = value!=0
472 elif offset==0x088c: # Engine #1 throttle
473 self._setThrottle(self.ENGINE_1, value)
474 elif offset==0x0924: # Engine #2 throttle
475 self._setThrottle(self.ENGINE_2, value)
476 elif offset==0x09bc: # Engine #3 throttle
477 self._setThrottle(self.ENGINE_3, value)
478 elif offset==0x0af4: # Fuel weight
479 self.fuelWeight = value / 256.0
480 elif offset==0x0b74: # Centre tank level
481 self._setFuelLevel(self.FUEL_CENTRE, value)
482 elif offset==0x0b78: # Centre tank capacity
483 self._setFuelCapacity(self.FUEL_CENTRE, value)
484 elif offset==0x0b7c: # Left tank level
485 self._setFuelLevel(self.FUEL_LEFT, value)
486 elif offset==0x0b80: # Left tank capacity
487 self._setFuelCapacity(self.FUEL_LEFT, value)
488 elif offset==0x0b84: # Left aux tank level
489 self._setFuelLevel(self.FUEL_LEFT_AUX, value)
490 elif offset==0x0b88: # Left aux tank capacity
491 self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
492 elif offset==0x0b8c: # Left tip tank level
493 self._setFuelLevel(self.FUEL_LEFT_TIP, value)
494 elif offset==0x0b90: # Left tip tank capacity
495 self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
496 elif offset==0x0b94: # Right aux tank level
497 self._setFuelLevel(self.FUEL_RIGHT, value)
498 elif offset==0x0b98: # Right aux tank capacity
499 self._setFuelCapacity(self.FUEL_RIGHT, value)
500 elif offset==0x0b9c: # Right tank level
501 self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
502 elif offset==0x0ba0: # Right tank capacity
503 self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
504 elif offset==0x0ba4: # Right tip tank level
505 self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
506 elif offset==0x0ba8: # Right tip tank capacity
507 self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
508 elif offset==0x0bc8: # Parking
509 self.parking = value!=0
510 elif offset==0x0bcc: # Spoilers armed
511 self.spoilersArmed = value!=0
512 elif offset==0x0bd0: # Spoilers
513 self.spoilters = 0 if value==0 \
514 else (value - 4800) / (16383 - 4800)
515 elif offset==0x0bdc: # Flaps control
516 numNotchesM1 = len(self.flapsNotches) - 1
517 flapsIncrement = 16383.0 / numNotchesM1
518 index = value / flapsIncrement
519 if index>=numNotchesM1:
520 self.flapsControl = self.flapsNotches[-1]
521 else:
522 self.flapsControl = self.flapsNotches[index]
523 self.flapsControl += value * \
524 (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
525 flapsIncrement
526 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
527 self.flaps = value * self.flapsNotches[-1] / 16383.0
528 elif offset==0x0bec: # Nose gear
529 self.noseGear = value / 16383.0
530 elif offset==0x0d0c: # Lights
531 self.navLightsOn = (lights&0x01)!=0
532 self.antiCollisionLightsOn = (lights&0x02)!=0
533 self.landingLightsOn = (lights&0x04)!=0
534 self.strobeLightsOn = (lights&0x10)!=0
535 elif offset==0x0e90: # Wind speed
536 self.windSpeed = value
537 elif offset==0x0e92: # Wind direction
538 self.windDirection = value * 360.0 / 65536.0
539 elif offset==0x11ba: # G-Load
540 self.gLoad = value / 625.0
541 elif offset==0x11c6: # Mach
542 # FIXME: calculate IAS using the altitude and QNH
543 self.ias = value / 0.05 / 20480
544 elif offset==0x1244: # Centre 2 tank level
545 self._setFuelLevel(self.FUEL_CENTRE_2, value)
546 elif offset==0x1248: # Centre 2 tank capacity
547 self._setFuelCapacity(self.FUEL_CENTRE_2, value)
548 elif offset==0x1254: # External 1 tank level
549 self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
550 elif offset==0x1258: # External 1 tank capacity
551 self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
552 elif offset==0x125c: # External 2 tank level
553 self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
554 elif offset==0x1260: # External 2 tank capacity
555 self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
556 elif offset==0x2000: # Engine #1 N1
557 self.n1[self.ENGINE_1] = value
558 elif offset==0x2100: # Engine #2 N1
559 self.n1[self.ENGINE_2] = value
560 elif offset==0x2200: # Engine #3 N1
561 self.n1[self.ENGINE_3] = value
562 elif offset==0x30c0: # Gross weight
563 raise FSUIPCException(ERR_DATA)
564 elif offset==0x31e4: # Radio altitude
565 raise FSUIPCException(ERR_DATA)
566 elif offset==0x3364: # Frozen
567 self.frozen = value!=0
568 elif offset==0x3bfc: # ZFW
569 self.zfw = value * const.LBSTOKG / 256.0
570 print "ZFW:", self.zfw
571 elif offset==0x3c00: # Path of the current AIR file
572 self.airPath = value
573 elif offset==0x3d00: # Name of the current aircraft
574 self.aircraftName = value
575 else:
576 print "Unhandled offset: %04x" % (offset,)
577 raise FSUIPCException(ERR_DATA)
578
579 def _readUTC(self):
580 """Read the UTC time.
581
582 The current offset is added to it."""
583 return time.gmtime(time.time() + self._timeOffset)
584
585 def _getFuelLevel(self, index):
586 """Get the fuel level for the fuel tank with the given
587 index."""
588 return 0 if self.fuelCapacities[index]==0.0 else \
589 int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
590
591 def _getFuelCapacity(self, index):
592 """Get the capacity of the fuel tank with the given index."""
593 return int(self.fuelCapacities[index] * const.KGSTOLB / self.fuelWeight)
594
595 def _getThrottle(self, index):
596 """Get the throttle value for the given index."""
597 return int(self.throttles[index] * 16383.0)
598
599 def _updateTimeOffset(self, index, value):
600 """Update the time offset if the value in the tm structure is replaced
601 by the given value."""
602 tm = self._readUTC()
603 tm1 = tm[:index] + (value,) + tm[(index+1):]
604 self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
605
606 def _setThrottle(self, index, value):
607 """Set the throttle value for the given index."""
608 self.throttles[index] = value / 16383.0
609
610 def _setFuelLevel(self, index, value):
611 """Set the fuel level for the fuel tank with the given index."""
612 self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
613 65536.0 / 128.0
614
615 def _setFuelCapacity(self, index, value):
616 """Set the capacity of the fuel tank with the given index."""
617 self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
618
619
620#------------------------------------------------------------------------------
621
622values = Values()
623
624#------------------------------------------------------------------------------
625
626def open(request):
627 """Open the connection."""
628 return True
629
630#------------------------------------------------------------------------------
631
632def prepare_data(pattern, forRead = True):
633 """Prepare the given pattern for reading and/or writing."""
634 return pattern
635
636#------------------------------------------------------------------------------
637
638def read(data):
639 """Read the given data."""
640 return [values.read(offset) for (offset, type) in data]
641
642#------------------------------------------------------------------------------
643
644def write(data):
645 """Write the given data."""
646 for (offset, type, value) in data:
647 values.write(offset, value)
648
649#------------------------------------------------------------------------------
650
651def close():
652 """Close the connection."""
653 pass
654
655#------------------------------------------------------------------------------
656
657PORT=15015
658
659CALL_READ=1
660CALL_WRITE=2
661CALL_CLOSE=3
662
663RESULT_RETURNED=1
664RESULT_EXCEPTION=2
665
666#------------------------------------------------------------------------------
667
668class Server(threading.Thread):
669 """The server thread."""
670 def __init__(self):
671 """Construct the thread."""
672 super(Server, self).__init__()
673 self.daemon = True
674
675 def run(self):
676 """Perform the server's operation."""
677 serverSocket = socket.socket()
678
679 serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
680 serverSocket.bind(("", PORT))
681
682 serverSocket.listen(5)
683
684 while True:
685 (clientSocket, clientAddress) = serverSocket.accept()
686 thread = threading.Thread(target = self._process, args=(clientSocket,))
687 thread.start()
688
689 def _process(self, clientSocket):
690 """Process the commands arriving on the given socket."""
691 socketFile = clientSocket.makefile()
692 try:
693 while True:
694 (length,) = struct.unpack("I", clientSocket.recv(4))
695 data = clientSocket.recv(length)
696 (call, args) = cPickle.loads(data)
697 exception = None
698
699 try:
700 if call==CALL_READ:
701 result = read(args[0])
702 elif call==CALL_WRITE:
703 result = write(args[0])
704 else:
705 break
706 except Exception, e:
707 exception = e
708
709 if exception is None:
710 data = cPickle.dumps((RESULT_RETURNED, result))
711 else:
712 data = cPickle.dumps((RESULT_EXCEPTION, exception))
713 clientSocket.send(struct.pack("I", len(data)) + data)
714 except Exception, e:
715 print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
716 finally:
717 try:
718 socketFile.close()
719 except:
720 pass
721 clientSocket.close()
722
723#------------------------------------------------------------------------------
724
725class Client(object):
726 """Client to the server."""
727 def __init__(self, serverHost):
728 """Construct the client and connect to the given server
729 host."""
730 self._socket = socket.socket()
731 self._socket.connect((serverHost, PORT))
732
733 self._socketFile = self._socket.makefile()
734
735 def read(self, data):
736 """Read the given data."""
737 return self._call(CALL_READ, data)
738
739 def write(self, data):
740 """Write the given data."""
741 return self._call(CALL_WRITE, data)
742
743 def _call(self, command, data):
744 """Perform a call with the given command and data."""
745 data = cPickle.dumps((command, [data]))
746 self._socket.send(struct.pack("I", len(data)) + data)
747 (length,) = struct.unpack("I", self._socket.recv(4))
748 data = self._socket.recv(length)
749 (resultCode, result) = cPickle.loads(data)
750 if resultCode==RESULT_RETURNED:
751 return result
752 else:
753 raise result
754
755#------------------------------------------------------------------------------
756#------------------------------------------------------------------------------
757
758# FIXME: implement proper completion and history
759class CLI(cmd.Cmd):
760 """The command-line interpreter."""
761 @staticmethod
762 def str2bool(s):
763 """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
764 return 1 if s in ["yes", "true", "on"] else 0
765
766 @staticmethod
767 def bool2str(value):
768 """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
769 return "no" if value==0 else "yes"
770
771 @staticmethod
772 def degree2pyuipc(degree):
773 """Convert the given degree (as a string) into a PyUIPC value."""
774 return int(float(degree) * 65536.0 * 65536.0 / 360.0)
775
776 @staticmethod
777 def pyuipc2degree(value):
778 """Convert the given PyUIPC value into a degree."""
779 return valie * 360.0 / 65536.0 / 65536.0
780
781 @staticmethod
782 def fuelLevel2pyuipc(level):
783 """Convert the given percentage value (as a string) into a PyUIPC value."""
784 return int(float(level) * 128.0 * 65536.0 / 100.0)
785
786 @staticmethod
787 def pyuipc2fuelLevel(value):
788 """Convert the PyUIPC value into a percentage value."""
789 return value * 100.0 / 128.0 / 65536.0
790
791 @staticmethod
792 def fuelCapacity2pyuipc(capacity):
793 """Convert the given capacity value (as a string) into a PyUIPC value."""
794 return int(capacity)
795
796 @staticmethod
797 def pyuipc2fuelCapacity(value):
798 """Convert the given capacity value into a PyUIPC value."""
799 return value
800
801 @staticmethod
802 def throttle2pyuipc(throttle):
803 """Convert the given throttle value (as a string) into a PyUIPC value."""
804 return int(float(throttle) * 16384.0 / 100.0)
805
806 @staticmethod
807 def pyuipc2throttle(value):
808 """Convert the given PyUIPC value into a throttle value."""
809 return value * 100.0 / 16384.0
810
811 def __init__(self):
812 """Construct the CLI."""
813 cmd.Cmd.__init__(self)
814
815 self.use_rawinput = True
816 self.intro = "\nPyUIPC simulator command prompt\n"
817 self.prompt = "PyUIPC> "
818
819 self.daemon = True
820
821 self._client = Client("localhost")
822
823 self._valueHandlers = {}
824 self._valueHandlers["year"] = (0x0240, "H", lambda value: value,
825 lambda word: int(word))
826 self._valueHandlers["yday"] = (0x023e, "H", lambda value: value,
827 lambda word: int(word))
828 self._valueHandlers["hour"] = (0x023b, "b", lambda value: value,
829 lambda word: int(word))
830 self._valueHandlers["min"] = (0x023c, "b", lambda value: value,
831 lambda word: int(word))
832 self._valueHandlers["sec"] = (0x023a, "b", lambda value: value,
833 lambda word: int(word))
834 self._valueHandlers["acftName"] = (0x3d00, -256, lambda value: value,
835 lambda word: word)
836 self._valueHandlers["airPath"] = (0x3c00, -256, lambda value: value,
837 lambda word: word)
838 self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
839 self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
840 self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
841 self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
842 self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
843 self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
844 self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
845 self._valueHandlers["zfw"] = (0x3bfc, "d",
846 lambda value: value * const.LBSTOKG / 256.0,
847 lambda word: int(float(word) * 256.0 *
848 const.KGSTOLB))
849 self._valueHandlers["grossWeight"] = (0x30c0, "f",
850 lambda value: value * const.LBSTOKG,
851 lambda word: None)
852 self._valueHandlers["heading"] = (0x0580, "d",
853 CLI.pyuipc2degree, CLI.degree2pyuipc)
854 self._valueHandlers["pitch"] = (0x0578, "d",
855 CLI.pyuipc2degree, CLI.degree2pyuipc)
856 self._valueHandlers["bank"] = (0x057c, "d",
857 CLI.pyuipc2degree, CLI.degree2pyuipc)
858 self._valueHandlers["ias"] = (0x02bc, "d",
859 lambda value: value / 128.0,
860 lambda word: int(float(word) * 128.0))
861 self._valueHandlers["mach"] = (0x11c6, "H",
862 lambda value: value / 20480.0,
863 lambda word: int(float(word) * 20480.0))
864 self._valueHandlers["gs"] = (0x02b4, "d",
865 lambda value: value * 3600.0 / 65536.0 / 1852.0,
866 lambda word: int(float(word) * 65536.0 *
867 1852.0 / 3600))
868 self._valueHandlers["vs"] = (0x02c8, "d",
869 lambda value: value * 60 /
870 const.FEETTOMETRES / 256.0,
871 lambda word: int(float(word) *
872 const.FEETTOMETRES *
873 256.0 / 60.0))
874 self._valueHandlers["radioAltitude"] = (0x31e4, "d",
875 lambda value: value /
876 const.FEETTOMETRES /
877 65536.0,
878 lambda word: int(float(word) *
879 const.FEETTOMETRES *
880 65536.0))
881 self._valueHandlers["altitude"] = (0x0570, "l",
882 lambda value: value /
883 const.FEETTOMETRES / 65536.0 /
884 65536.0,
885 lambda word: long(float(word) *
886 const.FEETTOMETRES *
887 65536.0 * 65536.0))
888 self._valueHandlers["gLoad"] = (0x11ba, "H",
889 lambda value: value / 625.0,
890 lambda word: int(float(word) * 625.0))
891
892 self._valueHandlers["flapsControl"] = (0x0bdc, "d",
893 lambda value: value * 100.0 / 16383.0,
894 lambda word: int(float(word) *
895 16383.0 / 100.0))
896 self._valueHandlers["flaps"] = (0x0be0, "d",
897 lambda value: value * 100.0 / 16383.0,
898 lambda word: int(float(word) *
899 16383.0 / 100.0))
900 self._valueHandlers["lights"] = (0x0d0c, "H",
901 lambda value: value,
902 lambda word: int(word))
903 self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
904 self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
905 self._valueHandlers["noseGear"] = (0x0bec, "d",
906 lambda value: value * 100.0 / 16383.0,
907 lambda word: int(float(word) *
908 16383.0 / 100.0))
909 self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
910 CLI.bool2str, CLI.str2bool)
911 self._valueHandlers["spoilers"] = (0x0bd0, "d",
912 lambda value: value,
913 lambda word: int(word))
914 self._valueHandlers["qnh"] = (0x0330, "H",
915 lambda value: value / 16.0,
916 lambda word: int(float(word)*16.0))
917 self._valueHandlers["nav1"] = (0x0350, "H",
918 Values._writeFrequency,
919 lambda word: Values._readFrequency(float(word)))
920 self._valueHandlers["nav2"] = (0x0352, "H",
921 Values._writeFrequency,
922 lambda word: Values._readFrequency(float(word)))
923 self._valueHandlers["squawk"] = (0x0354, "H",
924 Values._writeBCD,
925 lambda word: Values._readBCD(int(word)))
926 self._valueHandlers["windSpeed"] = (0x0e90, "H",
927 lambda value: value,
928 lambda word: int(word))
929 self._valueHandlers["windDirection"] = (0x0e92, "H",
930 lambda value: value * 360.0 / 65536.0,
931 lambda word: int(int(word) *
932 65536.0 / 360.0))
933 self._valueHandlers["fuelWeight"] = (0x0af4, "H",
934 lambda value: value / 256.0,
935 lambda word: int(float(word)*256.0))
936
937
938 self._valueHandlers["centreLevel"] = (0x0b74, "d", CLI.pyuipc2fuelLevel,
939 CLI.fuelLevel2pyuipc)
940 self._valueHandlers["centreCapacity"] = (0x0b78, "d",
941 CLI.pyuipc2fuelCapacity,
942 CLI.fuelCapacity2pyuipc)
943 self._valueHandlers["leftMainLevel"] = (0x0b7c, "d", CLI.pyuipc2fuelLevel,
944 CLI.fuelLevel2pyuipc)
945 self._valueHandlers["leftMainCapacity"] = (0x0b80, "d",
946 CLI.pyuipc2fuelCapacity,
947 CLI.fuelCapacity2pyuipc)
948 self._valueHandlers["leftAuxLevel"] = (0x0b84, "d", CLI.pyuipc2fuelLevel,
949 CLI.fuelLevel2pyuipc)
950 self._valueHandlers["leftAuxCapacity"] = (0x0b88, "d",
951 CLI.pyuipc2fuelCapacity,
952 CLI.fuelCapacity2pyuipc)
953 self._valueHandlers["leftTipLevel"] = (0x0b8c, "d", CLI.pyuipc2fuelLevel,
954 CLI.fuelLevel2pyuipc)
955 self._valueHandlers["leftTipCapacity"] = (0x0b90, "d",
956 CLI.pyuipc2fuelCapacity,
957 CLI.fuelCapacity2pyuipc)
958 self._valueHandlers["rightMainLevel"] = (0x0b94, "d", CLI.pyuipc2fuelLevel,
959 CLI.fuelLevel2pyuipc)
960 self._valueHandlers["rightMainCapacity"] = (0x0b98, "d",
961 CLI.pyuipc2fuelCapacity,
962 CLI.fuelCapacity2pyuipc)
963 self._valueHandlers["rightAuxLevel"] = (0x0b9c, "d", CLI.pyuipc2fuelLevel,
964 CLI.fuelLevel2pyuipc)
965 self._valueHandlers["rightAuxCapacity"] = (0x0ba0, "d",
966 CLI.pyuipc2fuelCapacity,
967 CLI.fuelCapacity2pyuipc)
968 self._valueHandlers["rightTipLevel"] = (0x0ba4, "d", CLI.pyuipc2fuelLevel,
969 CLI.fuelLevel2pyuipc)
970 self._valueHandlers["rightTipCapacity"] = (0x0ba8, "d",
971 CLI.pyuipc2fuelCapacity,
972 CLI.fuelCapacity2pyuipc)
973 self._valueHandlers["centre2Level"] = (0x1244, "d", CLI.pyuipc2fuelLevel,
974 CLI.fuelLevel2pyuipc)
975 self._valueHandlers["centre2Capacity"] = (0x1248, "d",
976 CLI.pyuipc2fuelCapacity,
977 CLI.fuelCapacity2pyuipc)
978 self._valueHandlers["external1Level"] = (0x1254, "d", CLI.pyuipc2fuelLevel,
979 CLI.fuelLevel2pyuipc)
980 self._valueHandlers["external1Capacity"] = (0x1258, "d",
981 CLI.pyuipc2fuelCapacity,
982 CLI.fuelCapacity2pyuipc)
983 self._valueHandlers["external2Level"] = (0x125c, "d", CLI.pyuipc2fuelLevel,
984 CLI.fuelLevel2pyuipc)
985 self._valueHandlers["external2Capacity"] = (0x1260, "d",
986 CLI.pyuipc2fuelCapacity,
987 CLI.fuelCapacity2pyuipc)
988
989 self._valueHandlers["n1_1"] = (0x2000, "f", lambda value: value,
990 lambda word: float(word))
991 self._valueHandlers["n1_2"] = (0x2100, "f", lambda value: value,
992 lambda word: float(word))
993 self._valueHandlers["n1_3"] = (0x2200, "f", lambda value: value,
994 lambda word: float(word))
995
996 self._valueHandlers["throttle_1"] = (0x088c, "H",
997 CLI.pyuipc2throttle,
998 CLI.throttle2pyuipc)
999 self._valueHandlers["throttle_2"] = (0x0924, "H",
1000 CLI.pyuipc2throttle,
1001 CLI.throttle2pyuipc)
1002 self._valueHandlers["throttle_3"] = (0x09bc, "H",
1003 CLI.pyuipc2throttle,
1004 CLI.throttle2pyuipc)
1005
1006 def default(self, line):
1007 """Handle unhandle commands."""
1008 if line=="EOF":
1009 print
1010 return True
1011 else:
1012 return super(CLI, self).default(line)
1013
1014 def do_get(self, args):
1015 """Handle the get command."""
1016 names = args.split()
1017 data = []
1018 for name in names:
1019 if name not in self._valueHandlers:
1020 print >> sys.stderr, "Unknown variable: " + name
1021 return False
1022 valueHandler = self._valueHandlers[name]
1023 data.append((valueHandler[0], valueHandler[1]))
1024
1025 try:
1026 result = self._client.read(data)
1027 for i in range(0, len(result)):
1028 name = names[i]
1029 valueHandler = self._valueHandlers[name]
1030 print name + "=" + str(valueHandler[2](result[i]))
1031 except Exception, e:
1032 print >> sys.stderr, "Failed to read data: " + str(e)
1033
1034 return False
1035
1036 def help_get(self):
1037 """Print help for the get command."""
1038 print "get <variable> [<variable>...]"
1039
1040 def complete_get(self, text, line, begidx, endidx):
1041 """Try to complete the get command."""
1042 return [key for key in self._valueHandlers if key.startswith(text)]
1043
1044 def do_set(self, args):
1045 """Handle the set command."""
1046 arguments = args.split()
1047 names = []
1048 data = []
1049 for argument in arguments:
1050 words = argument.split("=")
1051 if len(words)!=2:
1052 print >> sys.stderr, "Invalid argument: " + argument
1053 return False
1054
1055 (name, value) = words
1056 if name not in self._valueHandlers:
1057 print >> sys.stderr, "Unknown variable: " + name
1058 return False
1059
1060 valueHandler = self._valueHandlers[name]
1061 try:
1062 value = valueHandler[3](value)
1063 data.append((valueHandler[0], valueHandler[1], value))
1064 except Exception, e:
1065 print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
1066 (value, name, str(e))
1067 return False
1068
1069 try:
1070 self._client.write(data)
1071 print "Data written"
1072 except Exception, e:
1073 print >> sys.stderr, "Failed to write data: " + str(e)
1074
1075 return False
1076
1077 def help_set(self):
1078 """Print help for the set command."""
1079 print "set <variable>=<value> [<variable>=<value>...]"
1080
1081 def complete_set(self, text, line, begidx, endidx):
1082 """Try to complete the set command."""
1083 if not text and begidx>0 and line[begidx-1]=="=":
1084 return []
1085 else:
1086 return [key + "=" for key in self._valueHandlers if key.startswith(text)]
1087
1088 def do_quit(self, args):
1089 """Handle the quit command."""
1090 return True
1091
1092#------------------------------------------------------------------------------
1093
1094if __name__ == "__main__":
1095 CLI().cmdloop()
1096else:
1097 server = Server()
1098 server.start()
1099
1100#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.