source: src/mlx/pyuipc_sim.py@ 85:42b688827d63

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

Added the handling of the TD rate and the visiblity

File size: 50.5 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 self.tdRate = 0.0
208
209 self.radioAltitude = None
210 self.altitude = 513.0
211
212 self.gLoad = 1.0
213
214 self.flapsControl = 0.0
215 self.flaps = 0.0
216
217 self.navLightsOn = True
218 self.antiCollisionLightsOn = False
219 self.landingLightsOn = False
220 self.strobeLightsOn = False
221
222 self.pitot = False
223 self.parking = True
224
225 self.noseGear = 1.0
226
227 self.spoilersArmed = False
228 self.spoilers = 0.0
229
230 self.altimeter = 1013.0
231
232 self.nav1 = 117.3
233 self.nav2 = 109.5
234 self.squawk = 2200
235
236 self.windSpeed = 8.0
237 self.windDirection = 300.0
238 self.visibility = 10000
239
240 self.n1 = [0.0, 0.0, 0.0]
241 self.throttles = [0.0, 0.0, 0.0]
242
243 def read(self, offset):
244 """Read the value at the given offset."""
245 try:
246 return self._read(offset)
247 except Exception, e:
248 print "failed to read offset %04x: %s" % (offset, str(e))
249 raise FSUIPCException(ERR_DATA)
250
251 def _read(self, offset):
252 """Read the value at the given offset."""
253 if offset==0x023a: # Second of time
254 return self._readUTC().tm_sec
255 elif offset==0x023b: # Hour of Zulu time
256 return self._readUTC().tm_hour
257 elif offset==0x023c: # Minute of Zulu time
258 return self._readUTC().tm_min
259 elif offset==0x023e: # Day number on year
260 return self._readUTC().tm_yday
261 elif offset==0x0240: # Year in FS
262 return self._readUTC().tm_year
263 elif offset==0x0264: # Paused
264 return 1 if self.paused else 0
265 elif offset==0x029c: # Pitot
266 return 1 if self.pitot else 0
267 elif offset==0x02b4: # Ground speed
268 # FIXME: calculate TAS first, then from the heading and
269 # wind the GS
270 return int(self.ias * 65536.0 * 1852.0 / 3600.0)
271 elif offset==0x02b8: # TAS
272 return int(self._getTAS() * 128.0)
273 elif offset==0x02bc: # IAS
274 return int(self.ias * 128.0)
275 elif offset==0x02c8: # VS
276 return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
277 elif offset==0x030c: # TD rate
278 return int(self.tdRate * const.FEETTOMETRES * 256.0 / 60.0)
279 elif offset==0x0330: # Altimeter
280 return int(self.altimeter * 16.0)
281 elif offset==0x0350: # NAV1
282 return Values._readFrequency(self.nav1)
283 elif offset==0x0352: # NAV2
284 return Values._readFrequency(self.nav2)
285 elif offset==0x0354: # Squawk
286 return Values._readBCD(self.squawk)
287 elif offset==0x0366: # On the ground
288 return 1 if self.onTheGround else 0
289 elif offset==0x036c: # Stalled
290 return 1 if self.stalled else 0
291 elif offset==0x036d: # Overspeed
292 return 1 if self.overspeed else 0
293 elif offset==0x0570: # Altitude
294 return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
295 elif offset==0x0578: # Pitch
296 return int(self.pitch * 65536.0 * 65536.0 / 360.0)
297 elif offset==0x057c: # Bank
298 return int(self.bank * 65536.0 * 65536.0 / 360.0)
299 elif offset==0x0580: # Heading
300 return int(self.heading * 65536.0 * 65536.0 / 360.0)
301 elif offset==0x05dc: # Slew
302 return 1 if self.slew else 0
303 elif offset==0x0628: # Replay
304 return 1 if self.replay else 0
305 elif offset==0x088c: # Engine #1 throttle
306 return self._getThrottle(self.ENGINE_1)
307 elif offset==0x0924: # Engine #2 throttle
308 return self._getThrottle(self.ENGINE_2)
309 elif offset==0x09bc: # Engine #3 throttle
310 return self._getThrottle(self.ENGINE_3)
311 elif offset==0x0af4: # Fuel weight
312 return int(self.fuelWeight * 256.0)
313 elif offset==0x0b74: # Centre tank level
314 return self._getFuelLevel(self.FUEL_CENTRE)
315 elif offset==0x0b78: # Centre tank capacity
316 return self._getFuelCapacity(self.FUEL_CENTRE)
317 elif offset==0x0b7c: # Left tank level
318 return self._getFuelLevel(self.FUEL_LEFT)
319 elif offset==0x0b80: # Left tank capacity
320 return self._getFuelCapacity(self.FUEL_LEFT)
321 elif offset==0x0b84: # Left aux tank level
322 return self._getFuelLevel(self.FUEL_LEFT_AUX)
323 elif offset==0x0b88: # Left aux tank capacity
324 return self._getFuelCapacity(self.FUEL_LEFT_AUX)
325 elif offset==0x0b8c: # Left tip tank level
326 return self._getFuelLevel(self.FUEL_LEFT_TIP)
327 elif offset==0x0b90: # Left tip tank capacity
328 return self._getFuelCapacity(self.FUEL_LEFT_TIP)
329 elif offset==0x0b94: # Right aux tank level
330 return self._getFuelLevel(self.FUEL_RIGHT)
331 elif offset==0x0b98: # Right aux tank capacity
332 return self._getFuelCapacity(self.FUEL_RIGHT)
333 elif offset==0x0b9c: # Right tank level
334 return self._getFuelLevel(self.FUEL_RIGHT_AUX)
335 elif offset==0x0ba0: # Right tank capacity
336 return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
337 elif offset==0x0ba4: # Right tip tank level
338 return self._getFuelLevel(self.FUEL_RIGHT_TIP)
339 elif offset==0x0ba8: # Right tip tank capacity
340 return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
341 elif offset==0x0bc8: # Parking
342 return 1 if self.parking else 0
343 elif offset==0x0bcc: # Spoilers armed
344 return 1 if self.spoilersArmed else 0
345 elif offset==0x0bd0: # Spoilers
346 return 0 if self.spoilers == 0 \
347 else int(self.spoilers * (16383 - 4800) + 4800)
348 elif offset==0x0bdc: # Flaps control
349 numNotchesM1 = len(self.flapsNotches) - 1
350 flapsIncrement = 16383.0 / numNotchesM1
351 index = 0
352 while index<numNotchesM1 and \
353 self.flapsControl<self.flapsNotches[index]:
354 index += 1
355
356 if index==numNotchesM1:
357 return 16383
358 else:
359 return int((self.flapsControl-self.flapsNotches[index]) * \
360 flapsIncrement / \
361 (self.flapsNotches[index+1] - self.flapsNotches[index]))
362 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
363 return self.flaps * 16383.0 / self.flapsNotches[-1]
364 elif offset==0x0bec: # Nose gear
365 return int(self.noseGear * 16383.0)
366 elif offset==0x0d0c: # Lights
367 lights = 0
368 if self.navLightsOn: lights |= 0x01
369 if self.antiCollisionLightsOn: lights |= 0x02
370 if self.landingLightsOn: lights |= 0x04
371 if self.strobeLightsOn: lights |= 0x10
372 return lights
373 elif offset==0x0e8a: # Visibility
374 return int(self.visibility * 100.0 / 1609.344)
375 elif offset==0x0e90: # Wind speed
376 return int(self.windSpeed)
377 elif offset==0x0e92: # Wind direction
378 return int(self.windDirection * 65536.0 / 360.0)
379 elif offset==0x11ba: # G-Load
380 return int(self.gLoad * 625.0)
381 elif offset==0x11c6: # Mach
382 # FIXME: calculate from IAS, altitude and QNH
383 return int(self.ias * 0.05 * 20480.)
384 elif offset==0x1244: # Centre 2 tank level
385 return self._getFuelLevel(self.FUEL_CENTRE_2)
386 elif offset==0x1248: # Centre 2 tank capacity
387 return self._getFuelCapacity(self.FUEL_CENTRE_2)
388 elif offset==0x1254: # External 1 tank level
389 return self._getFuelLevel(self.FUEL_EXTERNAL_1)
390 elif offset==0x1258: # External 1 tank capacity
391 return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
392 elif offset==0x125c: # External 2 tank level
393 return self._getFuelLevel(self.FUEL_EXTERNAL_2)
394 elif offset==0x1260: # External 2 tank capacity
395 return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
396 elif offset==0x2000: # Engine #1 N1
397 return self.n1[self.ENGINE_1]
398 elif offset==0x2100: # Engine #2 N1
399 return self.n1[self.ENGINE_2]
400 elif offset==0x2200: # Engine #3 N1
401 return self.n1[self.ENGINE_3]
402 elif offset==0x30c0: # Gross weight
403 return (self.zfw + sum(self.fuelWeights)) * const.KGSTOLB
404 elif offset==0x31e4: # Radio altitude
405 # FIXME: if self.radioAltitude is None, calculate from the
406 # altitude with some, perhaps random, ground altitude
407 # value
408 radioAltitude = (self.altitude - 517) \
409 if self.radioAltitude is None else self.radioAltitude
410 return (radioAltitude * const.FEETTOMETRES * 65536.0)
411 elif offset==0x3364: # Frozen
412 return 1 if self.frozen else 0
413 elif offset==0x3bfc: # ZFW
414 return int(self.zfw * 256.0 * const.KGSTOLB)
415 elif offset==0x3c00: # Path of the current AIR file
416 return self.airPath
417 elif offset==0x3d00: # Name of the current aircraft
418 return self.aircraftName
419 else:
420 print "Unhandled offset: %04x" % (offset,)
421 raise FSUIPCException(ERR_DATA)
422
423 def write(self, offset, value):
424 """Write the value at the given offset."""
425 try:
426 return self._write(offset, value)
427 except Exception, e:
428 print "failed to write offset %04x: %s" % (offset, str(e))
429 raise FSUIPCException(ERR_DATA)
430
431 def _write(self, offset, value):
432 """Write the given value at the given offset."""
433 if offset==0x023a: # Second of time
434 self._updateTimeOffset(5, value)
435 elif offset==0x023b: # Hour of Zulu time
436 self._updateTimeOffset(3, value)
437 elif offset==0x023c: # Minute of Zulu time
438 self._updateTimeOffset(4, value)
439 elif offset==0x023e: # Day number in the year
440 self._updateTimeOffset(7, value)
441 elif offset==0x0240: # Year in FS
442 self._updateTimeOffset(0, value)
443 elif offset==0x0264: # Paused
444 self.paused = value!=0
445 elif offset==0x029c: # Pitot
446 self.pitot = value!=0
447 elif offset==0x02b4: # Ground speed
448 # FIXME: calculate TAS using the heading and the wind, and
449 # then IAS based on the altitude
450 self.ias = value * 3600.0 / 65536.0 / 1852.0
451 elif offset==0x02bc: # IAS
452 self.ias = value / 128.0
453 elif offset==0x02c8: # VS
454 self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
455 if not self.onTheGround:
456 self.tdRate = self.vs
457 elif offset==0x0330: # Altimeter
458 self.altimeter = value / 16.0
459 elif offset==0x0350: # NAV1
460 self.nav1 = Values._writeFrequency(value)
461 elif offset==0x0352: # NAV2
462 self.nav2 = Values._writeFrequency(value)
463 elif offset==0x0354: # Squawk
464 self.squawk = Values._writeBCD(value)
465 elif offset==0x0366: # On the groud
466 self.onTheGround = value!=0
467 if not self.onTheGround:
468 self.tdRate = self.vs
469 elif offset==0x036c: # Stalled
470 self.stalled = value!=0
471 elif offset==0x036d: # Overspeed
472 self.overspeed = value!=0
473 elif offset==0x0570: # Altitude
474 self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
475 elif offset==0x0578: # Pitch
476 self.pitch = value * 360.0 / 65536.0 / 65536.0
477 elif offset==0x057c: # Bank
478 self.bank = value * 360.0 / 65536.0 / 65536.0
479 elif offset==0x0580: # Heading
480 self.heading = value * 360.0 / 65536.0 / 65536.0
481 elif offset==0x05dc: # Slew
482 self.slew = value!=0
483 elif offset==0x0628: # Replay
484 self.replay = value!=0
485 elif offset==0x088c: # Engine #1 throttle
486 self._setThrottle(self.ENGINE_1, value)
487 elif offset==0x0924: # Engine #2 throttle
488 self._setThrottle(self.ENGINE_2, value)
489 elif offset==0x09bc: # Engine #3 throttle
490 self._setThrottle(self.ENGINE_3, value)
491 elif offset==0x0af4: # Fuel weight
492 self.fuelWeight = value / 256.0
493 elif offset==0x0b74: # Centre tank level
494 self._setFuelLevel(self.FUEL_CENTRE, value)
495 elif offset==0x0b78: # Centre tank capacity
496 self._setFuelCapacity(self.FUEL_CENTRE, value)
497 elif offset==0x0b7c: # Left tank level
498 self._setFuelLevel(self.FUEL_LEFT, value)
499 elif offset==0x0b80: # Left tank capacity
500 self._setFuelCapacity(self.FUEL_LEFT, value)
501 elif offset==0x0b84: # Left aux tank level
502 self._setFuelLevel(self.FUEL_LEFT_AUX, value)
503 elif offset==0x0b88: # Left aux tank capacity
504 self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
505 elif offset==0x0b8c: # Left tip tank level
506 self._setFuelLevel(self.FUEL_LEFT_TIP, value)
507 elif offset==0x0b90: # Left tip tank capacity
508 self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
509 elif offset==0x0b94: # Right aux tank level
510 self._setFuelLevel(self.FUEL_RIGHT, value)
511 elif offset==0x0b98: # Right aux tank capacity
512 self._setFuelCapacity(self.FUEL_RIGHT, value)
513 elif offset==0x0b9c: # Right tank level
514 self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
515 elif offset==0x0ba0: # Right tank capacity
516 self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
517 elif offset==0x0ba4: # Right tip tank level
518 self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
519 elif offset==0x0ba8: # Right tip tank capacity
520 self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
521 elif offset==0x0bc8: # Parking
522 self.parking = value!=0
523 elif offset==0x0bcc: # Spoilers armed
524 self.spoilersArmed = value!=0
525 elif offset==0x0bd0: # Spoilers
526 self.spoilters = 0 if value==0 \
527 else (value - 4800) / (16383 - 4800)
528 elif offset==0x0bdc: # Flaps control
529 numNotchesM1 = len(self.flapsNotches) - 1
530 flapsIncrement = 16383.0 / numNotchesM1
531 index = value / flapsIncrement
532 if index>=numNotchesM1:
533 self.flapsControl = self.flapsNotches[-1]
534 else:
535 self.flapsControl = self.flapsNotches[index]
536 self.flapsControl += value * \
537 (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
538 flapsIncrement
539 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
540 self.flaps = value * self.flapsNotches[-1] / 16383.0
541 elif offset==0x0bec: # Nose gear
542 self.noseGear = value / 16383.0
543 elif offset==0x0d0c: # Lights
544 self.navLightsOn = (value&0x01)!=0
545 self.antiCollisionLightsOn = (value&0x02)!=0
546 self.landingLightsOn = (value&0x04)!=0
547 self.strobeLightsOn = (value&0x10)!=0
548 elif offset==0x0e8a: # Visibility
549 self.visibility = value * 1609.344 / 100.0
550 elif offset==0x0e90: # Wind speed
551 self.windSpeed = value
552 elif offset==0x0e92: # Wind direction
553 self.windDirection = value * 360.0 / 65536.0
554 elif offset==0x11ba: # G-Load
555 self.gLoad = value / 625.0
556 elif offset==0x11c6: # Mach
557 # FIXME: calculate IAS using the altitude and QNH
558 self.ias = value / 0.05 / 20480
559 elif offset==0x1244: # Centre 2 tank level
560 self._setFuelLevel(self.FUEL_CENTRE_2, value)
561 elif offset==0x1248: # Centre 2 tank capacity
562 self._setFuelCapacity(self.FUEL_CENTRE_2, value)
563 elif offset==0x1254: # External 1 tank level
564 self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
565 elif offset==0x1258: # External 1 tank capacity
566 self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
567 elif offset==0x125c: # External 2 tank level
568 self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
569 elif offset==0x1260: # External 2 tank capacity
570 self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
571 elif offset==0x2000: # Engine #1 N1
572 self.n1[self.ENGINE_1] = value
573 elif offset==0x2100: # Engine #2 N1
574 self.n1[self.ENGINE_2] = value
575 elif offset==0x2200: # Engine #3 N1
576 self.n1[self.ENGINE_3] = value
577 elif offset==0x30c0: # Gross weight
578 raise FSUIPCException(ERR_DATA)
579 elif offset==0x31e4: # Radio altitude
580 raise FSUIPCException(ERR_DATA)
581 elif offset==0x3364: # Frozen
582 self.frozen = value!=0
583 elif offset==0x3bfc: # ZFW
584 self.zfw = value * const.LBSTOKG / 256.0
585 print "ZFW:", self.zfw
586 elif offset==0x3c00: # Path of the current AIR file
587 self.airPath = value
588 elif offset==0x3d00: # Name of the current aircraft
589 self.aircraftName = value
590 else:
591 print "Unhandled offset: %04x" % (offset,)
592 raise FSUIPCException(ERR_DATA)
593
594 def _readUTC(self):
595 """Read the UTC time.
596
597 The current offset is added to it."""
598 return time.gmtime(time.time() + self._timeOffset)
599
600 def _getFuelLevel(self, index):
601 """Get the fuel level for the fuel tank with the given
602 index."""
603 return 0 if self.fuelCapacities[index]==0.0 else \
604 int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
605
606 def _getFuelCapacity(self, index):
607 """Get the capacity of the fuel tank with the given index."""
608 return int(self.fuelCapacities[index] * const.KGSTOLB / self.fuelWeight)
609
610 def _getThrottle(self, index):
611 """Get the throttle value for the given index."""
612 return int(self.throttles[index] * 16383.0)
613
614 def _updateTimeOffset(self, index, value):
615 """Update the time offset if the value in the tm structure is replaced
616 by the given value."""
617 tm = self._readUTC()
618 tm1 = tm[:index] + (value,) + tm[(index+1):]
619 self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
620
621 def _setThrottle(self, index, value):
622 """Set the throttle value for the given index."""
623 self.throttles[index] = value / 16383.0
624
625 def _setFuelLevel(self, index, value):
626 """Set the fuel level for the fuel tank with the given index."""
627 self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
628 65536.0 / 128.0
629
630 def _setFuelCapacity(self, index, value):
631 """Set the capacity of the fuel tank with the given index."""
632 self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
633
634 def _getTAS(self):
635 """Calculate the true airspeed."""
636 pressure = 101325 * math.pow(1 - 2.25577e-5 * self.altitude *
637 const.FEETTOMETRES,
638 5.25588)
639 temperature = 15 - self.altitude * 6.5 * const.FEETTOMETRES / 1000.0
640 temperature += 273.15 # Celsius -> Kelvin
641 airDensity = pressure / (temperature * 287.05)
642 print "pressure:", pressure, "temperature:", temperature, "airDensity:", airDensity
643 return self.ias * math.sqrt(1.225 / airDensity)
644
645#------------------------------------------------------------------------------
646
647values = Values()
648
649#------------------------------------------------------------------------------
650
651failOpen = False
652
653opened = False
654
655#------------------------------------------------------------------------------
656
657def open(request):
658 """Open the connection."""
659 global opened
660 if failOpen:
661 raise FSUIPCException(ERR_NOFS)
662 elif opened:
663 raise FSUIPCException(ERR_OPEN)
664 else:
665 time.sleep(0.5)
666 opened = True
667 return True
668
669#------------------------------------------------------------------------------
670
671def prepare_data(pattern, forRead = True):
672 """Prepare the given pattern for reading and/or writing."""
673 if opened:
674 return pattern
675 else:
676 raise FSUIPCException(ERR_OPEN)
677
678#------------------------------------------------------------------------------
679
680def read(data):
681 """Read the given data."""
682 if opened:
683 return [values.read(offset) for (offset, type) in data]
684 else:
685 raise FSUIPCException(ERR_OPEN)
686
687#------------------------------------------------------------------------------
688
689def write(data):
690 """Write the given data."""
691 if opened:
692 for (offset, type, value) in data:
693 values.write(offset, value)
694 else:
695 raise FSUIPCException(ERR_OPEN)
696
697#------------------------------------------------------------------------------
698
699def close():
700 """Close the connection."""
701 global opened
702 opened = False
703
704#------------------------------------------------------------------------------
705
706PORT=15015
707
708CALL_READ=1
709CALL_WRITE=2
710CALL_CLOSE=3
711CALL_FAILOPEN=4
712CALL_QUIT = 99
713
714RESULT_RETURNED=1
715RESULT_EXCEPTION=2
716
717#------------------------------------------------------------------------------
718
719class Server(threading.Thread):
720 """The server thread."""
721 def __init__(self):
722 """Construct the thread."""
723 super(Server, self).__init__()
724 self.daemon = True
725
726 def run(self):
727 """Perform the server's operation."""
728 serverSocket = socket.socket()
729
730 serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
731 serverSocket.bind(("", PORT))
732
733 serverSocket.listen(5)
734
735 while True:
736 (clientSocket, clientAddress) = serverSocket.accept()
737 thread = threading.Thread(target = self._process, args=(clientSocket,))
738 thread.start()
739
740 def _process(self, clientSocket):
741 """Process the commands arriving on the given socket."""
742 socketFile = clientSocket.makefile()
743 try:
744 while True:
745 (length,) = struct.unpack("I", clientSocket.recv(4))
746 data = clientSocket.recv(length)
747 (call, args) = cPickle.loads(data)
748 exception = None
749
750 try:
751 if call==CALL_READ:
752 result = read(args[0])
753 elif call==CALL_WRITE:
754 result = write(args[0])
755 elif call==CALL_CLOSE:
756 global opened
757 opened = False
758 result = None
759 elif call==CALL_FAILOPEN:
760 global failOpen
761 failOpen = args[0]
762 result = None
763 else:
764 break
765 except Exception, e:
766 exception = e
767
768 if exception is None:
769 data = cPickle.dumps((RESULT_RETURNED, result))
770 else:
771 data = cPickle.dumps((RESULT_EXCEPTION, exception))
772 clientSocket.send(struct.pack("I", len(data)) + data)
773 except Exception, e:
774 print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
775 finally:
776 try:
777 socketFile.close()
778 except:
779 pass
780 clientSocket.close()
781
782#------------------------------------------------------------------------------
783
784class Client(object):
785 """Client to the server."""
786 def __init__(self, serverHost):
787 """Construct the client and connect to the given server
788 host."""
789 self._socket = socket.socket()
790 self._socket.connect((serverHost, PORT))
791
792 self._socketFile = self._socket.makefile()
793
794 def read(self, data):
795 """Read the given data."""
796 return self._call(CALL_READ, data)
797
798 def write(self, data):
799 """Write the given data."""
800 return self._call(CALL_WRITE, data)
801
802 def close(self):
803 """Close the connection currently opened in the simulator."""
804 return self._call(CALL_CLOSE, None)
805
806 def failOpen(self, really):
807 """Enable/disable open failure in the simulator."""
808 return self._call(CALL_FAILOPEN, really)
809
810 def quit(self):
811 """Quit from the simulator."""
812 data = cPickle.dumps((CALL_QUIT, None))
813 self._socket.send(struct.pack("I", len(data)) + data)
814
815 def _call(self, command, data):
816 """Perform a call with the given command and data."""
817 data = cPickle.dumps((command, [data]))
818 self._socket.send(struct.pack("I", len(data)) + data)
819 (length,) = struct.unpack("I", self._socket.recv(4))
820 data = self._socket.recv(length)
821 (resultCode, result) = cPickle.loads(data)
822 if resultCode==RESULT_RETURNED:
823 return result
824 else:
825 raise result
826
827#------------------------------------------------------------------------------
828#------------------------------------------------------------------------------
829
830# FIXME: implement proper completion and history
831class CLI(cmd.Cmd):
832 """The command-line interpreter."""
833 @staticmethod
834 def str2bool(s):
835 """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
836 return 1 if s in ["yes", "true", "on"] else 0
837
838 @staticmethod
839 def bool2str(value):
840 """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
841 return "no" if value==0 else "yes"
842
843 @staticmethod
844 def degree2pyuipc(degree):
845 """Convert the given degree (as a string) into a PyUIPC value."""
846 return int(float(degree) * 65536.0 * 65536.0 / 360.0)
847
848 @staticmethod
849 def pyuipc2degree(value):
850 """Convert the given PyUIPC value into a degree."""
851 return valie * 360.0 / 65536.0 / 65536.0
852
853 @staticmethod
854 def fuelLevel2pyuipc(level):
855 """Convert the given percentage value (as a string) into a PyUIPC value."""
856 return int(float(level) * 128.0 * 65536.0 / 100.0)
857
858 @staticmethod
859 def pyuipc2fuelLevel(value):
860 """Convert the PyUIPC value into a percentage value."""
861 return value * 100.0 / 128.0 / 65536.0
862
863 @staticmethod
864 def fuelCapacity2pyuipc(capacity):
865 """Convert the given capacity value (as a string) into a PyUIPC value."""
866 return int(capacity)
867
868 @staticmethod
869 def pyuipc2fuelCapacity(value):
870 """Convert the given capacity value into a PyUIPC value."""
871 return value
872
873 @staticmethod
874 def throttle2pyuipc(throttle):
875 """Convert the given throttle value (as a string) into a PyUIPC value."""
876 return int(float(throttle) * 16384.0 / 100.0)
877
878 @staticmethod
879 def pyuipc2throttle(value):
880 """Convert the given PyUIPC value into a throttle value."""
881 return value * 100.0 / 16384.0
882
883 def __init__(self):
884 """Construct the CLI."""
885 cmd.Cmd.__init__(self)
886
887 self.use_rawinput = True
888 self.intro = "\nPyUIPC simulator command prompt\n"
889 self.prompt = "PyUIPC> "
890
891 self.daemon = True
892
893 self._client = Client("localhost")
894
895 self._valueHandlers = {}
896 self._valueHandlers["year"] = (0x0240, "H", lambda value: value,
897 lambda word: int(word))
898 self._valueHandlers["yday"] = (0x023e, "H", lambda value: value,
899 lambda word: int(word))
900 self._valueHandlers["hour"] = (0x023b, "b", lambda value: value,
901 lambda word: int(word))
902 self._valueHandlers["min"] = (0x023c, "b", lambda value: value,
903 lambda word: int(word))
904 self._valueHandlers["sec"] = (0x023a, "b", lambda value: value,
905 lambda word: int(word))
906 self._valueHandlers["acftName"] = (0x3d00, -256, lambda value: value,
907 lambda word: word)
908 self._valueHandlers["airPath"] = (0x3c00, -256, lambda value: value,
909 lambda word: word)
910 self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
911 self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
912 self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
913 self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
914 self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
915 self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
916 self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
917 self._valueHandlers["zfw"] = (0x3bfc, "d",
918 lambda value: value * const.LBSTOKG / 256.0,
919 lambda word: int(float(word) * 256.0 *
920 const.KGSTOLB))
921 self._valueHandlers["grossWeight"] = (0x30c0, "f",
922 lambda value: value * const.LBSTOKG,
923 lambda word: None)
924 self._valueHandlers["heading"] = (0x0580, "d",
925 CLI.pyuipc2degree, CLI.degree2pyuipc)
926 self._valueHandlers["pitch"] = (0x0578, "d",
927 CLI.pyuipc2degree, CLI.degree2pyuipc)
928 self._valueHandlers["bank"] = (0x057c, "d",
929 CLI.pyuipc2degree, CLI.degree2pyuipc)
930 self._valueHandlers["ias"] = (0x02bc, "d",
931 lambda value: value / 128.0,
932 lambda word: int(float(word) * 128.0))
933 self._valueHandlers["mach"] = (0x11c6, "H",
934 lambda value: value / 20480.0,
935 lambda word: int(float(word) * 20480.0))
936 self._valueHandlers["gs"] = (0x02b4, "d",
937 lambda value: value * 3600.0 / 65536.0 / 1852.0,
938 lambda word: int(float(word) * 65536.0 *
939 1852.0 / 3600))
940 self._valueHandlers["tas"] = (0x02b8, "d",
941 lambda value: value / 128.0,
942 lambda word: None)
943 self._valueHandlers["vs"] = (0x02c8, "d",
944 lambda value: value * 60 /
945 const.FEETTOMETRES / 256.0,
946 lambda word: int(float(word) *
947 const.FEETTOMETRES *
948 256.0 / 60.0))
949 self._valueHandlers["tdRate"] = (0x030c, "d",
950 lambda value: value * 60 /
951 const.FEETTOMETRES / 256.0,
952 lambda word: int(float(word) *
953 const.FEETTOMETRES *
954 256.0 / 60.0))
955 self._valueHandlers["radioAltitude"] = (0x31e4, "d",
956 lambda value: value /
957 const.FEETTOMETRES /
958 65536.0,
959 lambda word: int(float(word) *
960 const.FEETTOMETRES *
961 65536.0))
962 self._valueHandlers["altitude"] = (0x0570, "l",
963 lambda value: value /
964 const.FEETTOMETRES / 65536.0 /
965 65536.0,
966 lambda word: long(float(word) *
967 const.FEETTOMETRES *
968 65536.0 * 65536.0))
969 self._valueHandlers["gLoad"] = (0x11ba, "H",
970 lambda value: value / 625.0,
971 lambda word: int(float(word) * 625.0))
972
973 self._valueHandlers["flapsControl"] = (0x0bdc, "d",
974 lambda value: value * 100.0 / 16383.0,
975 lambda word: int(float(word) *
976 16383.0 / 100.0))
977 self._valueHandlers["flaps"] = (0x0be0, "d",
978 lambda value: value * 100.0 / 16383.0,
979 lambda word: int(float(word) *
980 16383.0 / 100.0))
981 self._valueHandlers["lights"] = (0x0d0c, "H",
982 lambda value: value,
983 lambda word: int(word))
984 self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
985 self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
986 self._valueHandlers["noseGear"] = (0x0bec, "d",
987 lambda value: value * 100.0 / 16383.0,
988 lambda word: int(float(word) *
989 16383.0 / 100.0))
990 self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
991 CLI.bool2str, CLI.str2bool)
992 self._valueHandlers["spoilers"] = (0x0bd0, "d",
993 lambda value: value,
994 lambda word: int(word))
995 self._valueHandlers["qnh"] = (0x0330, "H",
996 lambda value: value / 16.0,
997 lambda word: int(float(word)*16.0))
998 self._valueHandlers["nav1"] = (0x0350, "H",
999 Values._writeFrequency,
1000 lambda word: Values._readFrequency(float(word)))
1001 self._valueHandlers["nav2"] = (0x0352, "H",
1002 Values._writeFrequency,
1003 lambda word: Values._readFrequency(float(word)))
1004 self._valueHandlers["squawk"] = (0x0354, "H",
1005 Values._writeBCD,
1006 lambda word: Values._readBCD(int(word)))
1007 self._valueHandlers["windSpeed"] = (0x0e90, "H",
1008 lambda value: value,
1009 lambda word: int(word))
1010 self._valueHandlers["windDirection"] = (0x0e92, "H",
1011 lambda value: value * 360.0 / 65536.0,
1012 lambda word: int(int(word) *
1013 65536.0 / 360.0))
1014 self._valueHandlers["fuelWeight"] = (0x0af4, "H",
1015 lambda value: value / 256.0,
1016 lambda word: int(float(word)*256.0))
1017
1018
1019 self._valueHandlers["centreLevel"] = (0x0b74, "d", CLI.pyuipc2fuelLevel,
1020 CLI.fuelLevel2pyuipc)
1021 self._valueHandlers["centreCapacity"] = (0x0b78, "d",
1022 CLI.pyuipc2fuelCapacity,
1023 CLI.fuelCapacity2pyuipc)
1024 self._valueHandlers["leftMainLevel"] = (0x0b7c, "d", CLI.pyuipc2fuelLevel,
1025 CLI.fuelLevel2pyuipc)
1026 self._valueHandlers["leftMainCapacity"] = (0x0b80, "d",
1027 CLI.pyuipc2fuelCapacity,
1028 CLI.fuelCapacity2pyuipc)
1029 self._valueHandlers["leftAuxLevel"] = (0x0b84, "d", CLI.pyuipc2fuelLevel,
1030 CLI.fuelLevel2pyuipc)
1031 self._valueHandlers["leftAuxCapacity"] = (0x0b88, "d",
1032 CLI.pyuipc2fuelCapacity,
1033 CLI.fuelCapacity2pyuipc)
1034 self._valueHandlers["leftTipLevel"] = (0x0b8c, "d", CLI.pyuipc2fuelLevel,
1035 CLI.fuelLevel2pyuipc)
1036 self._valueHandlers["leftTipCapacity"] = (0x0b90, "d",
1037 CLI.pyuipc2fuelCapacity,
1038 CLI.fuelCapacity2pyuipc)
1039 self._valueHandlers["rightMainLevel"] = (0x0b94, "d", CLI.pyuipc2fuelLevel,
1040 CLI.fuelLevel2pyuipc)
1041 self._valueHandlers["rightMainCapacity"] = (0x0b98, "d",
1042 CLI.pyuipc2fuelCapacity,
1043 CLI.fuelCapacity2pyuipc)
1044 self._valueHandlers["rightAuxLevel"] = (0x0b9c, "d", CLI.pyuipc2fuelLevel,
1045 CLI.fuelLevel2pyuipc)
1046 self._valueHandlers["rightAuxCapacity"] = (0x0ba0, "d",
1047 CLI.pyuipc2fuelCapacity,
1048 CLI.fuelCapacity2pyuipc)
1049 self._valueHandlers["rightTipLevel"] = (0x0ba4, "d", CLI.pyuipc2fuelLevel,
1050 CLI.fuelLevel2pyuipc)
1051 self._valueHandlers["rightTipCapacity"] = (0x0ba8, "d",
1052 CLI.pyuipc2fuelCapacity,
1053 CLI.fuelCapacity2pyuipc)
1054 self._valueHandlers["centre2Level"] = (0x1244, "d", CLI.pyuipc2fuelLevel,
1055 CLI.fuelLevel2pyuipc)
1056 self._valueHandlers["centre2Capacity"] = (0x1248, "d",
1057 CLI.pyuipc2fuelCapacity,
1058 CLI.fuelCapacity2pyuipc)
1059 self._valueHandlers["external1Level"] = (0x1254, "d", CLI.pyuipc2fuelLevel,
1060 CLI.fuelLevel2pyuipc)
1061 self._valueHandlers["external1Capacity"] = (0x1258, "d",
1062 CLI.pyuipc2fuelCapacity,
1063 CLI.fuelCapacity2pyuipc)
1064 self._valueHandlers["external2Level"] = (0x125c, "d", CLI.pyuipc2fuelLevel,
1065 CLI.fuelLevel2pyuipc)
1066 self._valueHandlers["external2Capacity"] = (0x1260, "d",
1067 CLI.pyuipc2fuelCapacity,
1068 CLI.fuelCapacity2pyuipc)
1069
1070 self._valueHandlers["n1_1"] = (0x2000, "f", lambda value: value,
1071 lambda word: float(word))
1072 self._valueHandlers["n1_2"] = (0x2100, "f", lambda value: value,
1073 lambda word: float(word))
1074 self._valueHandlers["n1_3"] = (0x2200, "f", lambda value: value,
1075 lambda word: float(word))
1076
1077 self._valueHandlers["throttle_1"] = (0x088c, "H",
1078 CLI.pyuipc2throttle,
1079 CLI.throttle2pyuipc)
1080 self._valueHandlers["throttle_2"] = (0x0924, "H",
1081 CLI.pyuipc2throttle,
1082 CLI.throttle2pyuipc)
1083 self._valueHandlers["throttle_3"] = (0x09bc, "H",
1084 CLI.pyuipc2throttle,
1085 CLI.throttle2pyuipc)
1086
1087 self._valueHandlers["visibility"] = (0x0e8a, "H",
1088 lambda value: value*1609.344/100.0,
1089 lambda word: int(float(word)*
1090 100.0/1609.344))
1091
1092 def default(self, line):
1093 """Handle unhandle commands."""
1094 if line=="EOF":
1095 print
1096 return self.do_quit("")
1097 else:
1098 return super(CLI, self).default(line)
1099
1100 def do_get(self, args):
1101 """Handle the get command."""
1102 names = args.split()
1103 data = []
1104 for name in names:
1105 if name not in self._valueHandlers:
1106 print >> sys.stderr, "Unknown variable: " + name
1107 return False
1108 valueHandler = self._valueHandlers[name]
1109 data.append((valueHandler[0], valueHandler[1]))
1110
1111 try:
1112 result = self._client.read(data)
1113 for i in range(0, len(result)):
1114 name = names[i]
1115 valueHandler = self._valueHandlers[name]
1116 print name + "=" + str(valueHandler[2](result[i]))
1117 except Exception, e:
1118 print >> sys.stderr, "Failed to read data: " + str(e)
1119
1120 return False
1121
1122 def help_get(self):
1123 """Print help for the get command."""
1124 print "get <variable> [<variable>...]"
1125
1126 def complete_get(self, text, line, begidx, endidx):
1127 """Try to complete the get command."""
1128 return [key for key in self._valueHandlers if key.startswith(text)]
1129
1130 def do_set(self, args):
1131 """Handle the set command."""
1132 arguments = args.split()
1133 names = []
1134 data = []
1135 for argument in arguments:
1136 words = argument.split("=")
1137 if len(words)!=2:
1138 print >> sys.stderr, "Invalid argument: " + argument
1139 return False
1140
1141 (name, value) = words
1142 if name not in self._valueHandlers:
1143 print >> sys.stderr, "Unknown variable: " + name
1144 return False
1145
1146 valueHandler = self._valueHandlers[name]
1147 try:
1148 value = valueHandler[3](value)
1149 data.append((valueHandler[0], valueHandler[1], value))
1150 except Exception, e:
1151 print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
1152 (value, name, str(e))
1153 return False
1154
1155 try:
1156 self._client.write(data)
1157 print "Data written"
1158 except Exception, e:
1159 print >> sys.stderr, "Failed to write data: " + str(e)
1160
1161 return False
1162
1163 def help_set(self):
1164 """Print help for the set command."""
1165 print "set <variable>=<value> [<variable>=<value>...]"
1166
1167 def complete_set(self, text, line, begidx, endidx):
1168 """Try to complete the set command."""
1169 if not text and begidx>0 and line[begidx-1]=="=":
1170 return []
1171 else:
1172 return [key + "=" for key in self._valueHandlers if key.startswith(text)]
1173
1174 def do_close(self, args):
1175 """Close an existing connection so that FS will fail."""
1176 try:
1177 self._client.close()
1178 print "Connection closed"
1179 except Exception, e:
1180 print >> sys.stderr, "Failed to close the connection: " + str(e)
1181
1182 def do_failopen(self, args):
1183 """Enable/disable the failing of opens."""
1184 try:
1185 value = self.str2bool(args)
1186 self._client.failOpen(value)
1187 print "Opening will%s fail" % ("" if value else " not",)
1188 except Exception, e:
1189 print >> sys.stderr, "Failed to set open failure: " + str(e)
1190
1191 def help_failopen(self, usage = False):
1192 """Help for the failopen close"""
1193 if usage: print "Usage:",
1194 print "failopen yes|no"
1195
1196 def complete_failopen(self, text, line, begidx, endidx):
1197 if text:
1198 if "yes".startswith(text): return ["yes"]
1199 elif "no".startswith(text): return ["no"]
1200 else: return []
1201 else:
1202 return ["yes", "no"]
1203
1204 def do_quit(self, args):
1205 """Handle the quit command."""
1206 self._client.quit()
1207 return True
1208
1209#------------------------------------------------------------------------------
1210
1211if __name__ == "__main__":
1212 CLI().cmdloop()
1213else:
1214 server = Server()
1215 server.start()
1216
1217#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.