source: src/mlx/pyuipc_sim.py@ 57:04d8f6a603ee

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

TAS is calculated.

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