source: src/mlx/pyuipc_sim.py@ 55:f6a3ee5f8855

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

The CLI works now for most values

File size: 40.1 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 # FIXME: check for realistic values
198 self.fuelWeight = 3.5
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 def __init__(self):
782 """Construct the CLI."""
783 cmd.Cmd.__init__(self)
784
785 self.use_rawinput = True
786 self.intro = "\nPyUIPC simulator command prompt\n"
787 self.prompt = "PyUIPC> "
788
789 self.daemon = True
790
791 self._client = Client("localhost")
792
793 self._valueHandlers = {}
794 self._valueHandlers["year"] = (0x0240, "H", lambda value: value,
795 lambda word: int(word))
796 self._valueHandlers["yday"] = (0x023e, "H", lambda value: value,
797 lambda word: int(word))
798 self._valueHandlers["hour"] = (0x023b, "b", lambda value: value,
799 lambda word: int(word))
800 self._valueHandlers["min"] = (0x023c, "b", lambda value: value,
801 lambda word: int(word))
802 self._valueHandlers["sec"] = (0x023a, "b", lambda value: value,
803 lambda word: int(word))
804 self._valueHandlers["acftName"] = (0x3d00, -256, lambda value: value,
805 lambda word: word)
806 self._valueHandlers["airPath"] = (0x3c00, -256, lambda value: value,
807 lambda word: word)
808 self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
809 self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
810 self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
811 self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
812 self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
813 self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
814 self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
815 self._valueHandlers["zfw"] = (0x3bfc, "d",
816 lambda value: value * const.LBSTOKG / 256.0,
817 lambda word: int(float(word) * 256.0 *
818 const.KGSTOLB))
819 self._valueHandlers["grossWeight"] = (0x30c0, "f",
820 lambda value: value * const.LBSTOKG,
821 lambda word: None)
822 self._valueHandlers["heading"] = (0x0580, "d",
823 CLI.pyuipc2degree, CLI.degree2pyuipc)
824 self._valueHandlers["pitch"] = (0x0578, "d",
825 CLI.pyuipc2degree, CLI.degree2pyuipc)
826 self._valueHandlers["bank"] = (0x057c, "d",
827 CLI.pyuipc2degree, CLI.degree2pyuipc)
828 self._valueHandlers["ias"] = (0x02bc, "d",
829 lambda value: value / 128.0,
830 lambda word: int(float(word) * 128.0))
831 self._valueHandlers["mach"] = (0x11c6, "H",
832 lambda value: value / 20480.0,
833 lambda word: int(float(word) * 20480.0))
834 self._valueHandlers["gs"] = (0x02b4, "d",
835 lambda value: value * 3600.0 / 65536.0 / 1852.0,
836 lambda word: int(float(word) * 65536.0 *
837 1852.0 / 3600))
838 self._valueHandlers["vs"] = (0x02c8, "d",
839 lambda value: value * 60 /
840 const.FEETTOMETRES / 256.0,
841 lambda word: int(float(word) *
842 const.FEETTOMETRES *
843 256.0 / 60.0))
844 self._valueHandlers["radioAltitude"] = (0x31e4, "d",
845 lambda value: value /
846 const.FEETTOMETRES /
847 65536.0,
848 lambda word: int(float(word) *
849 const.FEETTOMETRES *
850 65536.0))
851 self._valueHandlers["altitude"] = (0x0570, "l",
852 lambda value: value /
853 const.FEETTOMETRES / 65536.0 /
854 65536.0,
855 lambda word: long(float(word) *
856 const.FEETTOMETRES *
857 65536.0 * 65536.0))
858 self._valueHandlers["gLoad"] = (0x11ba, "H",
859 lambda value: value / 625.0,
860 lambda word: int(float(word) * 625.0))
861
862 self._valueHandlers["flapsControl"] = (0x0bdc, "d",
863 lambda value: value * 100.0 / 16383.0,
864 lambda word: int(float(word) *
865 16383.0 / 100.0))
866 self._valueHandlers["flaps"] = (0x0be0, "d",
867 lambda value: value * 100.0 / 16383.0,
868 lambda word: int(float(word) *
869 16383.0 / 100.0))
870 self._valueHandlers["lights"] = (0x0d0c, "H",
871 lambda value: value,
872 lambda word: int(word))
873 self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
874 self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
875 self._valueHandlers["noseGear"] = (0x0bec, "d",
876 lambda value: value * 100.0 / 16383.0,
877 lambda word: int(float(word) *
878 16383.0 / 100.0))
879 self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
880 CLI.bool2str, CLI.str2bool)
881 self._valueHandlers["spoilers"] = (0x0bd0, "d",
882 lambda value: value,
883 lambda word: int(word))
884 self._valueHandlers["qnh"] = (0x0330, "H",
885 lambda value: value / 16.0,
886 lambda word: int(float(word)*16.0))
887 self._valueHandlers["nav1"] = (0x0350, "H",
888 Values._writeFrequency,
889 lambda word: Values._readFrequency(float(word)))
890 self._valueHandlers["nav2"] = (0x0352, "H",
891 Values._writeFrequency,
892 lambda word: Values._readFrequency(float(word)))
893 self._valueHandlers["squawk"] = (0x0354, "H",
894 Values._writeBCD,
895 lambda word: Values._readBCD(int(word)))
896 self._valueHandlers["windSpeed"] = (0x0e90, "H",
897 lambda value: value,
898 lambda word: int(word))
899 self._valueHandlers["windDirection"] = (0x0e92, "H",
900 lambda value: value * 360.0 / 65536.0,
901 lambda word: int(int(word) *
902 65536.0 / 360.0))
903
904 def default(self, line):
905 """Handle unhandle commands."""
906 if line=="EOF":
907 print
908 return True
909 else:
910 return super(CLI, self).default(line)
911
912 def do_get(self, args):
913 """Handle the get command."""
914 names = args.split()
915 data = []
916 for name in names:
917 if name not in self._valueHandlers:
918 print >> sys.stderr, "Unknown variable: " + name
919 return False
920 valueHandler = self._valueHandlers[name]
921 data.append((valueHandler[0], valueHandler[1]))
922
923 try:
924 result = self._client.read(data)
925 for i in range(0, len(result)):
926 name = names[i]
927 valueHandler = self._valueHandlers[name]
928 print name + "=" + str(valueHandler[2](result[i]))
929 except Exception, e:
930 print >> sys.stderr, "Failed to read data: " + str(e)
931
932 return False
933
934 def help_get(self):
935 """Print help for the get command."""
936 print "get <variable> [<variable>...]"
937
938 def complete_get(self, text, line, begidx, endidx):
939 """Try to complete the get command."""
940 return [key for key in self._valueHandlers if key.startswith(text)]
941
942 def do_set(self, args):
943 """Handle the set command."""
944 arguments = args.split()
945 names = []
946 data = []
947 for argument in arguments:
948 words = argument.split("=")
949 if len(words)!=2:
950 print >> sys.stderr, "Invalid argument: " + argument
951 return False
952
953 (name, value) = words
954 if name not in self._valueHandlers:
955 print >> sys.stderr, "Unknown variable: " + name
956 return False
957
958 valueHandler = self._valueHandlers[name]
959 try:
960 value = valueHandler[3](value)
961 data.append((valueHandler[0], valueHandler[1], value))
962 except Exception, e:
963 print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
964 (value, name, str(e))
965 return False
966
967 try:
968 self._client.write(data)
969 print "Data written"
970 except Exception, e:
971 print >> sys.stderr, "Failed to write data: " + str(e)
972
973 return False
974
975 def help_set(self):
976 """Print help for the set command."""
977 print "set <variable>=<value> [<variable>=<value>...]"
978
979 def complete_set(self, text, line, begidx, endidx):
980 """Try to complete the set command."""
981 if not text and begidx>0 and line[begidx-1]=="=":
982 return []
983 else:
984 return [key + "=" for key in self._valueHandlers if key.startswith(text)]
985
986 def do_quit(self, args):
987 """Handle the quit command."""
988 return True
989
990#------------------------------------------------------------------------------
991
992if __name__ == "__main__":
993 CLI().cmdloop()
994else:
995 server = Server()
996 server.start()
997
998#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.