source: src/mlx/pyuipc_sim.py@ 59:a3e0b8455dc8

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

Implemented better connection and connection failure handling.

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