source: src/mlx/pyuipc_sim.py@ 73:0108dbf6b53f

Last change on this file since 73:0108dbf6b53f was 61:7deb4cf7dd78, checked in by István Váradi <ivaradi@…>, 13 years ago

The time page is implemented too.

File size: 49.2 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
639failOpen = False
640
641opened = False
642
643#------------------------------------------------------------------------------
644
645def open(request):
646 """Open the connection."""
647 global opened
648 if failOpen:
649 raise FSUIPCException(ERR_NOFS)
650 elif opened:
651 raise FSUIPCException(ERR_OPEN)
652 else:
653 time.sleep(0.5)
654 opened = True
655 return True
656
657#------------------------------------------------------------------------------
658
659def prepare_data(pattern, forRead = True):
660 """Prepare the given pattern for reading and/or writing."""
661 if opened:
662 return pattern
663 else:
664 raise FSUIPCException(ERR_OPEN)
665
666#------------------------------------------------------------------------------
667
668def read(data):
669 """Read the given data."""
670 if opened:
671 return [values.read(offset) for (offset, type) in data]
672 else:
673 raise FSUIPCException(ERR_OPEN)
674
675#------------------------------------------------------------------------------
676
677def write(data):
678 """Write the given data."""
679 if opened:
680 for (offset, type, value) in data:
681 values.write(offset, value)
682 else:
683 raise FSUIPCException(ERR_OPEN)
684
685#------------------------------------------------------------------------------
686
687def close():
688 """Close the connection."""
689 global opened
690 opened = False
691
692#------------------------------------------------------------------------------
693
694PORT=15015
695
696CALL_READ=1
697CALL_WRITE=2
698CALL_CLOSE=3
699CALL_FAILOPEN=4
700CALL_QUIT = 99
701
702RESULT_RETURNED=1
703RESULT_EXCEPTION=2
704
705#------------------------------------------------------------------------------
706
707class Server(threading.Thread):
708 """The server thread."""
709 def __init__(self):
710 """Construct the thread."""
711 super(Server, self).__init__()
712 self.daemon = True
713
714 def run(self):
715 """Perform the server's operation."""
716 serverSocket = socket.socket()
717
718 serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
719 serverSocket.bind(("", PORT))
720
721 serverSocket.listen(5)
722
723 while True:
724 (clientSocket, clientAddress) = serverSocket.accept()
725 thread = threading.Thread(target = self._process, args=(clientSocket,))
726 thread.start()
727
728 def _process(self, clientSocket):
729 """Process the commands arriving on the given socket."""
730 socketFile = clientSocket.makefile()
731 try:
732 while True:
733 (length,) = struct.unpack("I", clientSocket.recv(4))
734 data = clientSocket.recv(length)
735 (call, args) = cPickle.loads(data)
736 exception = None
737
738 try:
739 if call==CALL_READ:
740 result = read(args[0])
741 elif call==CALL_WRITE:
742 result = write(args[0])
743 elif call==CALL_CLOSE:
744 global opened
745 opened = False
746 result = None
747 elif call==CALL_FAILOPEN:
748 global failOpen
749 failOpen = args[0]
750 result = None
751 else:
752 break
753 except Exception, e:
754 exception = e
755
756 if exception is None:
757 data = cPickle.dumps((RESULT_RETURNED, result))
758 else:
759 data = cPickle.dumps((RESULT_EXCEPTION, exception))
760 clientSocket.send(struct.pack("I", len(data)) + data)
761 except Exception, e:
762 print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
763 finally:
764 try:
765 socketFile.close()
766 except:
767 pass
768 clientSocket.close()
769
770#------------------------------------------------------------------------------
771
772class Client(object):
773 """Client to the server."""
774 def __init__(self, serverHost):
775 """Construct the client and connect to the given server
776 host."""
777 self._socket = socket.socket()
778 self._socket.connect((serverHost, PORT))
779
780 self._socketFile = self._socket.makefile()
781
782 def read(self, data):
783 """Read the given data."""
784 return self._call(CALL_READ, data)
785
786 def write(self, data):
787 """Write the given data."""
788 return self._call(CALL_WRITE, data)
789
790 def close(self):
791 """Close the connection currently opened in the simulator."""
792 return self._call(CALL_CLOSE, None)
793
794 def failOpen(self, really):
795 """Enable/disable open failure in the simulator."""
796 return self._call(CALL_FAILOPEN, really)
797
798 def quit(self):
799 """Quit from the simulator."""
800 data = cPickle.dumps((CALL_QUIT, None))
801 self._socket.send(struct.pack("I", len(data)) + data)
802
803 def _call(self, command, data):
804 """Perform a call with the given command and data."""
805 data = cPickle.dumps((command, [data]))
806 self._socket.send(struct.pack("I", len(data)) + data)
807 (length,) = struct.unpack("I", self._socket.recv(4))
808 data = self._socket.recv(length)
809 (resultCode, result) = cPickle.loads(data)
810 if resultCode==RESULT_RETURNED:
811 return result
812 else:
813 raise result
814
815#------------------------------------------------------------------------------
816#------------------------------------------------------------------------------
817
818# FIXME: implement proper completion and history
819class CLI(cmd.Cmd):
820 """The command-line interpreter."""
821 @staticmethod
822 def str2bool(s):
823 """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
824 return 1 if s in ["yes", "true", "on"] else 0
825
826 @staticmethod
827 def bool2str(value):
828 """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
829 return "no" if value==0 else "yes"
830
831 @staticmethod
832 def degree2pyuipc(degree):
833 """Convert the given degree (as a string) into a PyUIPC value."""
834 return int(float(degree) * 65536.0 * 65536.0 / 360.0)
835
836 @staticmethod
837 def pyuipc2degree(value):
838 """Convert the given PyUIPC value into a degree."""
839 return valie * 360.0 / 65536.0 / 65536.0
840
841 @staticmethod
842 def fuelLevel2pyuipc(level):
843 """Convert the given percentage value (as a string) into a PyUIPC value."""
844 return int(float(level) * 128.0 * 65536.0 / 100.0)
845
846 @staticmethod
847 def pyuipc2fuelLevel(value):
848 """Convert the PyUIPC value into a percentage value."""
849 return value * 100.0 / 128.0 / 65536.0
850
851 @staticmethod
852 def fuelCapacity2pyuipc(capacity):
853 """Convert the given capacity value (as a string) into a PyUIPC value."""
854 return int(capacity)
855
856 @staticmethod
857 def pyuipc2fuelCapacity(value):
858 """Convert the given capacity value into a PyUIPC value."""
859 return value
860
861 @staticmethod
862 def throttle2pyuipc(throttle):
863 """Convert the given throttle value (as a string) into a PyUIPC value."""
864 return int(float(throttle) * 16384.0 / 100.0)
865
866 @staticmethod
867 def pyuipc2throttle(value):
868 """Convert the given PyUIPC value into a throttle value."""
869 return value * 100.0 / 16384.0
870
871 def __init__(self):
872 """Construct the CLI."""
873 cmd.Cmd.__init__(self)
874
875 self.use_rawinput = True
876 self.intro = "\nPyUIPC simulator command prompt\n"
877 self.prompt = "PyUIPC> "
878
879 self.daemon = True
880
881 self._client = Client("localhost")
882
883 self._valueHandlers = {}
884 self._valueHandlers["year"] = (0x0240, "H", lambda value: value,
885 lambda word: int(word))
886 self._valueHandlers["yday"] = (0x023e, "H", lambda value: value,
887 lambda word: int(word))
888 self._valueHandlers["hour"] = (0x023b, "b", lambda value: value,
889 lambda word: int(word))
890 self._valueHandlers["min"] = (0x023c, "b", lambda value: value,
891 lambda word: int(word))
892 self._valueHandlers["sec"] = (0x023a, "b", lambda value: value,
893 lambda word: int(word))
894 self._valueHandlers["acftName"] = (0x3d00, -256, lambda value: value,
895 lambda word: word)
896 self._valueHandlers["airPath"] = (0x3c00, -256, lambda value: value,
897 lambda word: word)
898 self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
899 self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
900 self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
901 self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
902 self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
903 self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
904 self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
905 self._valueHandlers["zfw"] = (0x3bfc, "d",
906 lambda value: value * const.LBSTOKG / 256.0,
907 lambda word: int(float(word) * 256.0 *
908 const.KGSTOLB))
909 self._valueHandlers["grossWeight"] = (0x30c0, "f",
910 lambda value: value * const.LBSTOKG,
911 lambda word: None)
912 self._valueHandlers["heading"] = (0x0580, "d",
913 CLI.pyuipc2degree, CLI.degree2pyuipc)
914 self._valueHandlers["pitch"] = (0x0578, "d",
915 CLI.pyuipc2degree, CLI.degree2pyuipc)
916 self._valueHandlers["bank"] = (0x057c, "d",
917 CLI.pyuipc2degree, CLI.degree2pyuipc)
918 self._valueHandlers["ias"] = (0x02bc, "d",
919 lambda value: value / 128.0,
920 lambda word: int(float(word) * 128.0))
921 self._valueHandlers["mach"] = (0x11c6, "H",
922 lambda value: value / 20480.0,
923 lambda word: int(float(word) * 20480.0))
924 self._valueHandlers["gs"] = (0x02b4, "d",
925 lambda value: value * 3600.0 / 65536.0 / 1852.0,
926 lambda word: int(float(word) * 65536.0 *
927 1852.0 / 3600))
928 self._valueHandlers["tas"] = (0x02b8, "d",
929 lambda value: value / 128.0,
930 lambda word: None)
931 self._valueHandlers["vs"] = (0x02c8, "d",
932 lambda value: value * 60 /
933 const.FEETTOMETRES / 256.0,
934 lambda word: int(float(word) *
935 const.FEETTOMETRES *
936 256.0 / 60.0))
937 self._valueHandlers["radioAltitude"] = (0x31e4, "d",
938 lambda value: value /
939 const.FEETTOMETRES /
940 65536.0,
941 lambda word: int(float(word) *
942 const.FEETTOMETRES *
943 65536.0))
944 self._valueHandlers["altitude"] = (0x0570, "l",
945 lambda value: value /
946 const.FEETTOMETRES / 65536.0 /
947 65536.0,
948 lambda word: long(float(word) *
949 const.FEETTOMETRES *
950 65536.0 * 65536.0))
951 self._valueHandlers["gLoad"] = (0x11ba, "H",
952 lambda value: value / 625.0,
953 lambda word: int(float(word) * 625.0))
954
955 self._valueHandlers["flapsControl"] = (0x0bdc, "d",
956 lambda value: value * 100.0 / 16383.0,
957 lambda word: int(float(word) *
958 16383.0 / 100.0))
959 self._valueHandlers["flaps"] = (0x0be0, "d",
960 lambda value: value * 100.0 / 16383.0,
961 lambda word: int(float(word) *
962 16383.0 / 100.0))
963 self._valueHandlers["lights"] = (0x0d0c, "H",
964 lambda value: value,
965 lambda word: int(word))
966 self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
967 self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
968 self._valueHandlers["noseGear"] = (0x0bec, "d",
969 lambda value: value * 100.0 / 16383.0,
970 lambda word: int(float(word) *
971 16383.0 / 100.0))
972 self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
973 CLI.bool2str, CLI.str2bool)
974 self._valueHandlers["spoilers"] = (0x0bd0, "d",
975 lambda value: value,
976 lambda word: int(word))
977 self._valueHandlers["qnh"] = (0x0330, "H",
978 lambda value: value / 16.0,
979 lambda word: int(float(word)*16.0))
980 self._valueHandlers["nav1"] = (0x0350, "H",
981 Values._writeFrequency,
982 lambda word: Values._readFrequency(float(word)))
983 self._valueHandlers["nav2"] = (0x0352, "H",
984 Values._writeFrequency,
985 lambda word: Values._readFrequency(float(word)))
986 self._valueHandlers["squawk"] = (0x0354, "H",
987 Values._writeBCD,
988 lambda word: Values._readBCD(int(word)))
989 self._valueHandlers["windSpeed"] = (0x0e90, "H",
990 lambda value: value,
991 lambda word: int(word))
992 self._valueHandlers["windDirection"] = (0x0e92, "H",
993 lambda value: value * 360.0 / 65536.0,
994 lambda word: int(int(word) *
995 65536.0 / 360.0))
996 self._valueHandlers["fuelWeight"] = (0x0af4, "H",
997 lambda value: value / 256.0,
998 lambda word: int(float(word)*256.0))
999
1000
1001 self._valueHandlers["centreLevel"] = (0x0b74, "d", CLI.pyuipc2fuelLevel,
1002 CLI.fuelLevel2pyuipc)
1003 self._valueHandlers["centreCapacity"] = (0x0b78, "d",
1004 CLI.pyuipc2fuelCapacity,
1005 CLI.fuelCapacity2pyuipc)
1006 self._valueHandlers["leftMainLevel"] = (0x0b7c, "d", CLI.pyuipc2fuelLevel,
1007 CLI.fuelLevel2pyuipc)
1008 self._valueHandlers["leftMainCapacity"] = (0x0b80, "d",
1009 CLI.pyuipc2fuelCapacity,
1010 CLI.fuelCapacity2pyuipc)
1011 self._valueHandlers["leftAuxLevel"] = (0x0b84, "d", CLI.pyuipc2fuelLevel,
1012 CLI.fuelLevel2pyuipc)
1013 self._valueHandlers["leftAuxCapacity"] = (0x0b88, "d",
1014 CLI.pyuipc2fuelCapacity,
1015 CLI.fuelCapacity2pyuipc)
1016 self._valueHandlers["leftTipLevel"] = (0x0b8c, "d", CLI.pyuipc2fuelLevel,
1017 CLI.fuelLevel2pyuipc)
1018 self._valueHandlers["leftTipCapacity"] = (0x0b90, "d",
1019 CLI.pyuipc2fuelCapacity,
1020 CLI.fuelCapacity2pyuipc)
1021 self._valueHandlers["rightMainLevel"] = (0x0b94, "d", CLI.pyuipc2fuelLevel,
1022 CLI.fuelLevel2pyuipc)
1023 self._valueHandlers["rightMainCapacity"] = (0x0b98, "d",
1024 CLI.pyuipc2fuelCapacity,
1025 CLI.fuelCapacity2pyuipc)
1026 self._valueHandlers["rightAuxLevel"] = (0x0b9c, "d", CLI.pyuipc2fuelLevel,
1027 CLI.fuelLevel2pyuipc)
1028 self._valueHandlers["rightAuxCapacity"] = (0x0ba0, "d",
1029 CLI.pyuipc2fuelCapacity,
1030 CLI.fuelCapacity2pyuipc)
1031 self._valueHandlers["rightTipLevel"] = (0x0ba4, "d", CLI.pyuipc2fuelLevel,
1032 CLI.fuelLevel2pyuipc)
1033 self._valueHandlers["rightTipCapacity"] = (0x0ba8, "d",
1034 CLI.pyuipc2fuelCapacity,
1035 CLI.fuelCapacity2pyuipc)
1036 self._valueHandlers["centre2Level"] = (0x1244, "d", CLI.pyuipc2fuelLevel,
1037 CLI.fuelLevel2pyuipc)
1038 self._valueHandlers["centre2Capacity"] = (0x1248, "d",
1039 CLI.pyuipc2fuelCapacity,
1040 CLI.fuelCapacity2pyuipc)
1041 self._valueHandlers["external1Level"] = (0x1254, "d", CLI.pyuipc2fuelLevel,
1042 CLI.fuelLevel2pyuipc)
1043 self._valueHandlers["external1Capacity"] = (0x1258, "d",
1044 CLI.pyuipc2fuelCapacity,
1045 CLI.fuelCapacity2pyuipc)
1046 self._valueHandlers["external2Level"] = (0x125c, "d", CLI.pyuipc2fuelLevel,
1047 CLI.fuelLevel2pyuipc)
1048 self._valueHandlers["external2Capacity"] = (0x1260, "d",
1049 CLI.pyuipc2fuelCapacity,
1050 CLI.fuelCapacity2pyuipc)
1051
1052 self._valueHandlers["n1_1"] = (0x2000, "f", lambda value: value,
1053 lambda word: float(word))
1054 self._valueHandlers["n1_2"] = (0x2100, "f", lambda value: value,
1055 lambda word: float(word))
1056 self._valueHandlers["n1_3"] = (0x2200, "f", lambda value: value,
1057 lambda word: float(word))
1058
1059 self._valueHandlers["throttle_1"] = (0x088c, "H",
1060 CLI.pyuipc2throttle,
1061 CLI.throttle2pyuipc)
1062 self._valueHandlers["throttle_2"] = (0x0924, "H",
1063 CLI.pyuipc2throttle,
1064 CLI.throttle2pyuipc)
1065 self._valueHandlers["throttle_3"] = (0x09bc, "H",
1066 CLI.pyuipc2throttle,
1067 CLI.throttle2pyuipc)
1068
1069 def default(self, line):
1070 """Handle unhandle commands."""
1071 if line=="EOF":
1072 print
1073 return self.do_quit("")
1074 else:
1075 return super(CLI, self).default(line)
1076
1077 def do_get(self, args):
1078 """Handle the get command."""
1079 names = args.split()
1080 data = []
1081 for name in names:
1082 if name not in self._valueHandlers:
1083 print >> sys.stderr, "Unknown variable: " + name
1084 return False
1085 valueHandler = self._valueHandlers[name]
1086 data.append((valueHandler[0], valueHandler[1]))
1087
1088 try:
1089 result = self._client.read(data)
1090 for i in range(0, len(result)):
1091 name = names[i]
1092 valueHandler = self._valueHandlers[name]
1093 print name + "=" + str(valueHandler[2](result[i]))
1094 except Exception, e:
1095 print >> sys.stderr, "Failed to read data: " + str(e)
1096
1097 return False
1098
1099 def help_get(self):
1100 """Print help for the get command."""
1101 print "get <variable> [<variable>...]"
1102
1103 def complete_get(self, text, line, begidx, endidx):
1104 """Try to complete the get command."""
1105 return [key for key in self._valueHandlers if key.startswith(text)]
1106
1107 def do_set(self, args):
1108 """Handle the set command."""
1109 arguments = args.split()
1110 names = []
1111 data = []
1112 for argument in arguments:
1113 words = argument.split("=")
1114 if len(words)!=2:
1115 print >> sys.stderr, "Invalid argument: " + argument
1116 return False
1117
1118 (name, value) = words
1119 if name not in self._valueHandlers:
1120 print >> sys.stderr, "Unknown variable: " + name
1121 return False
1122
1123 valueHandler = self._valueHandlers[name]
1124 try:
1125 value = valueHandler[3](value)
1126 data.append((valueHandler[0], valueHandler[1], value))
1127 except Exception, e:
1128 print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
1129 (value, name, str(e))
1130 return False
1131
1132 try:
1133 self._client.write(data)
1134 print "Data written"
1135 except Exception, e:
1136 print >> sys.stderr, "Failed to write data: " + str(e)
1137
1138 return False
1139
1140 def help_set(self):
1141 """Print help for the set command."""
1142 print "set <variable>=<value> [<variable>=<value>...]"
1143
1144 def complete_set(self, text, line, begidx, endidx):
1145 """Try to complete the set command."""
1146 if not text and begidx>0 and line[begidx-1]=="=":
1147 return []
1148 else:
1149 return [key + "=" for key in self._valueHandlers if key.startswith(text)]
1150
1151 def do_close(self, args):
1152 """Close an existing connection so that FS will fail."""
1153 try:
1154 self._client.close()
1155 print "Connection closed"
1156 except Exception, e:
1157 print >> sys.stderr, "Failed to close the connection: " + str(e)
1158
1159 def do_failopen(self, args):
1160 """Enable/disable the failing of opens."""
1161 try:
1162 value = self.str2bool(args)
1163 self._client.failOpen(value)
1164 print "Opening will%s fail" % ("" if value else " not",)
1165 except Exception, e:
1166 print >> sys.stderr, "Failed to set open failure: " + str(e)
1167
1168 def help_failopen(self, usage = False):
1169 """Help for the failopen close"""
1170 if usage: print "Usage:",
1171 print "failopen yes|no"
1172
1173 def complete_failopen(self, text, line, begidx, endidx):
1174 if text:
1175 if "yes".startswith(text): return ["yes"]
1176 elif "no".startswith(text): return ["no"]
1177 else: return []
1178 else:
1179 return ["yes", "no"]
1180
1181 def do_quit(self, args):
1182 """Handle the quit command."""
1183 self._client.quit()
1184 return True
1185
1186#------------------------------------------------------------------------------
1187
1188if __name__ == "__main__":
1189 CLI().cmdloop()
1190else:
1191 server = Server()
1192 server.start()
1193
1194#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.