source: src/mlx/pyuipc_sim.py@ 54:6b4af8182384

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

It is now possible to write the values.

File size: 30.4 KB
Line 
1# Simulator for the pyuipc module
2#------------------------------------------------------------------------------
3
4import const
5
6import cmd
7import threading
8import socket
9import time
10import calendar
11import sys
12import struct
13import cPickle
14
15#------------------------------------------------------------------------------
16
17# Version constants
18SIM_ANY=0
19SIM_FS98=1
20SIM_FS2K=2
21SIM_CFS2=3
22SIM_CFS1=4
23SIM_FLY=5
24SIM_FS2K2=6
25SIM_FS2K4=7
26
27#------------------------------------------------------------------------------
28
29# Error constants
30ERR_OK=0
31ERR_OPEN=1
32ERR_NOFS=2
33ERR_REGMSG=3
34ERR_ATOM=4
35ERR_MAP=5
36ERR_VIEW=6
37ERR_VERSION=7
38ERR_WRONGFS=8
39ERR_NOTOPEN=9
40ERR_NODATA=10
41ERR_TIMEOUT=11
42ERR_SENDMSG=12
43ERR_DATA=13
44ERR_RUNNING=14
45ERR_SIZE=15
46
47#------------------------------------------------------------------------------
48
49# The version of FSUIPC
50fsuipc_version=0x0401
51lib_version=0x0302
52fs_version=SIM_FS2K4
53
54#------------------------------------------------------------------------------
55
56class FSUIPCException(Exception):
57 """FSUIPC exception class.
58
59 It contains a member variable named errorCode. The string is a text
60 describing the error."""
61
62 errors=["OK",
63 "Attempt to Open when already Open",
64 "Cannot link to FSUIPC or WideClient",
65 "Failed to Register common message with Windows",
66 "Failed to create Atom for mapping filename",
67 "Failed to create a file mapping object",
68 "Failed to open a view to the file map",
69 "Incorrect version of FSUIPC, or not FSUIPC",
70 "Sim is not version requested",
71 "Call cannot execute, link not Open",
72 "Call cannot execute: no requests accumulated",
73 "IPC timed out all retries",
74 "IPC sendmessage failed all retries",
75 "IPC request contains bad data",
76 "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
77 "Read or Write request cannot be added, memory for Process is full"]
78
79 def __init__(self, errorCode):
80 """
81 Construct the exception
82 """
83 if errorCode<len(self.errors):
84 self.errorString = self.errors[errorCode]
85 else:
86 self.errorString = "Unknown error"
87 Exception.__init__(self, self.errorString)
88 self.errorCode = errorCode
89
90 def __str__(self):
91 """
92 Convert the excption to string
93 """
94 return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
95
96#------------------------------------------------------------------------------
97
98class Values(object):
99 """The values that can be read from 'FSUIPC'."""
100 # Fuel data index: centre tank
101 FUEL_CENTRE = 0
102
103 # Fuel data index: left main tank
104 FUEL_LEFT = 1
105
106 # Fuel data index: right main tank
107 FUEL_RIGHT = 2
108
109 # Fuel data index: left aux tank
110 FUEL_LEFT_AUX = 3
111
112 # Fuel data index: right aux tank
113 FUEL_RIGHT_AUX = 4
114
115 # Fuel data index: left tip tank
116 FUEL_LEFT_TIP = 5
117
118 # Fuel data index: right tip tank
119 FUEL_RIGHT_AUX = 6
120
121 # Fuel data index: external 1 tank
122 FUEL_EXTERNAL_1 = 7
123
124 # Fuel data index: external 2 tank
125 FUEL_EXTERNAL_2 = 8
126
127 # Fuel data index: centre 2 tank
128 FUEL_CENTRE_2 = 9
129
130 # The number of fuel tank entries
131 NUM_FUEL = FUEL_CENTRE_2 + 1
132
133 # Engine index: engine #1
134 ENGINE_1 = 0
135
136 # Engine index: engine #2
137 ENGINE_2 = 1
138
139 # Engine index: engine #3
140 ENGINE_3 = 2
141
142 @staticmethod
143 def _readFrequency(frequency):
144 """Convert the given frequency into BCD."""
145 return Values._readBCD(int(frequency*100.0))
146
147 @staticmethod
148 def _readBCD(value):
149 """Convert the given value into BCD format."""
150 bcd = (value/1000) % 10
151 bcd <<= 4
152 bcd |= (value/100) % 10
153 bcd <<= 4
154 bcd |= (value/10) % 10
155 bcd <<= 4
156 bcd |= value % 10
157 return bcd
158
159 @staticmethod
160 def _writeFrequency(value):
161 """Convert the given value into a frequency."""
162 return (Values._writeBCD(value) + 10000) / 100.0
163
164 @staticmethod
165 def _writeBCD(value):
166 """Convert the given BCD value into a real value."""
167 bcd = (value>>24) & 0xf
168 bcd *= 10
169 bcd += (value>>16) & 0xf
170 bcd *= 10
171 bcd += (value>>8) & 0xf
172 bcd *= 10
173 bcd += (value>>0) & 0xf
174
175 return bcd
176
177 def __init__(self):
178 """Construct the values with defaults."""
179 self._timeOffset = 0
180 self.airPath = "C:\\Program Files\\Microsoft Games\\" \
181 "FS9\\Aircraft\\Cessna\\cessna172.air"
182 self.aircraftName = "Cessna 172SP"
183 self.flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40]
184 self.fuelCapacities = [10000.0, 5000.0, 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
185
186 self.paused = False
187 self.frozen = False
188 self.replay = False
189 self.slew = False
190 self.overspeed = False
191 self.stalled = False
192 self.onTheGround = True
193
194 self.zfw = 50000.0
195
196 self.fuelWeights = [0.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
197 # FIXME: check for realistic values
198 self.fuelWeight = 3.5
199
200 self.heading = 220.0
201 self.pitch = 0.0
202 self.bank = 0.0
203
204 self.ias = 0.0
205 self.vs = 0.0
206
207 self.radioAltitude = None
208 self.altitude = 513.0
209
210 self.gLoad = 1.0
211
212 self.flapsControl = 0.0
213 self.flaps = 0.0
214
215 self.navLightsOn = True
216 self.antiCollisionLightsOn = False
217 self.landingLightsOn = False
218 self.strobeLightsOn = False
219
220 self.pitot = False
221 self.parking = True
222
223 self.noseGear = 1.0
224
225 self.spoilersArmed = False
226 self.spoilers = 0.0
227
228 self.altimeter = 1013.0
229
230 self.nav1 = 117.3
231 self.nav2 = 109.5
232 self.squawk = 2200
233
234 self.windSpeed = 8.0
235 self.windDirection = 300.0
236
237 self.n1 = [0.0, 0.0, 0.0]
238 self.throttles = [0.0, 0.0, 0.0]
239
240 def read(self, offset):
241 """Read the value at the given offset."""
242 try:
243 return self._read(offset)
244 except Exception, e:
245 print "failed to read offset %04x: %s" % (offset, str(e))
246 raise FSUIPCException(ERR_DATA)
247
248 def _read(self, offset):
249 """Read the value at the given offset."""
250 if offset==0x023a: # Second of time
251 return self._readUTC().tm_sec
252 elif offset==0x023b: # Hour of Zulu time
253 return self._readUTC().tm_hour
254 elif offset==0x023c: # Minute of Zulu time
255 return self._readUTC().tm_min
256 elif offset==0x023e: # Day number on year
257 return self._readUTC().tm_yday
258 elif offset==0x0240: # Year in FS
259 return self._readUTC().tm_year
260 elif offset==0x0264: # Paused
261 return 1 if self.paused else 0
262 elif offset==0x029c: # Pitot
263 return 1 if self.pitot else 0
264 elif offset==0x02b4: # Ground speed
265 # FIXME: calculate TAS first, then from the heading and
266 # wind the GS
267 return int(self.ias * 65536.0 * 1852.0 / 3600.0)
268 elif offset==0x02bc: # IAS
269 return int(self.ias * 128.0)
270 elif offset==0x02c8: # VS
271 return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
272 elif offset==0x0330: # Altimeter
273 return int(self.altimeter * 16.0)
274 elif offset==0x0350: # NAV1
275 return Values._readFrequency(self.nav1)
276 elif offset==0x0352: # NAV2
277 return Values._readFrequency(self.nav2)
278 elif offset==0x0354: # Squawk
279 return Values._readBCD(self.squawk)
280 elif offset==0x0366: # On the ground
281 return 1 if self.onTheGround else 0
282 elif offset==0x036c: # Stalled
283 return 1 if self.stalled else 0
284 elif offset==0x036d: # Overspeed
285 return 1 if self.overspeed else 0
286 elif offset==0x0570: # Altitude
287 return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
288 elif offset==0x0578: # Pitch
289 return int(self.pitch * 65536.0 * 65536.0 / 360.0)
290 elif offset==0x057c: # Bank
291 return int(self.bank * 65536.0 * 65536.0 / 360.0)
292 elif offset==0x0580: # Heading
293 return int(self.heading * 65536.0 * 65536.0 / 360.0)
294 elif offset==0x05dc: # Slew
295 return 1 if self.slew else 0
296 elif offset==0x0628: # Replay
297 return 1 if self.replay else 0
298 elif offset==0x088c: # Engine #1 throttle
299 return self._getThrottle(self.ENGINE_1)
300 elif offset==0x0924: # Engine #2 throttle
301 return self._getThrottle(self.ENGINE_2)
302 elif offset==0x09bc: # Engine #3 throttle
303 return self._getThrottle(self.ENGINE_3)
304 elif offset==0x0af4: # Fuel weight
305 return int(self.fuelWeight * 256.0)
306 elif offset==0x0b74: # Centre tank level
307 return self._getFuelLevel(self.FUEL_CENTRE)
308 elif offset==0x0b78: # Centre tank capacity
309 return self._getFuelCapacity(self.FUEL_CENTRE)
310 elif offset==0x0b7c: # Left tank level
311 return self._getFuelLevel(self.FUEL_LEFT)
312 elif offset==0x0b80: # Left tank capacity
313 return self._getFuelCapacity(self.FUEL_LEFT)
314 elif offset==0x0b84: # Left aux tank level
315 return self._getFuelLevel(self.FUEL_LEFT_AUX)
316 elif offset==0x0b88: # Left aux tank capacity
317 return self._getFuelCapacity(self.FUEL_LEFT_AUX)
318 elif offset==0x0b8c: # Left tip tank level
319 return self._getFuelLevel(self.FUEL_LEFT_TIP)
320 elif offset==0x0b90: # Left tip tank capacity
321 return self._getFuelCapacity(self.FUEL_LEFT_TIP)
322 elif offset==0x0b94: # Right aux tank level
323 return self._getFuelLevel(self.FUEL_RIGHT)
324 elif offset==0x0b98: # Right aux tank capacity
325 return self._getFuelCapacity(self.FUEL_RIGHT)
326 elif offset==0x0b9c: # Right tank level
327 return self._getFuelLevel(self.FUEL_RIGHT_AUX)
328 elif offset==0x0ba0: # Right tank capacity
329 return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
330 elif offset==0x0ba4: # Right tip tank level
331 return self._getFuelLevel(self.FUEL_RIGHT_TIP)
332 elif offset==0x0ba8: # Right tip tank capacity
333 return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
334 elif offset==0x0bc8: # Parking
335 return 1 if self.parking else 0
336 elif offset==0x0bcc: # Spoilers armed
337 return 1 if self.spoilersArmed else 0
338 elif offset==0x0bd0: # Spoilers
339 return 0 if self.spoilers == 0 \
340 else int(self.spoilers * (16383 - 4800) + 4800)
341 elif offset==0x0bdc: # Flaps control
342 numNotchesM1 = len(self.flapsNotches) - 1
343 flapsIncrement = 16383.0 / numNotchesM1
344 index = 0
345 while index<numNotchesM1 and \
346 self.flapsControl<self.flapsNotches[index]:
347 index += 1
348
349 if index==numNotchesM1:
350 return 16383
351 else:
352 return int((self.flapsControl-self.flapsNotches[index]) * \
353 flapsIncrement / \
354 (self.flapsNotches[index+1] - self.flapsNotches[index]))
355 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
356 return self.flaps * 16383.0 / self.flapsNotches[-1]
357 elif offset==0x0bec: # Nose gear
358 return int(self.noseGear * 16383.0)
359 elif offset==0x0d0c: # Lights
360 lights = 0
361 if self.navLightsOn: lights |= 0x01
362 if self.antiCollisionLightsOn: lights |= 0x02
363 if self.landingLightsOn: lights |= 0x04
364 if self.strobeLightsOn: lights |= 0x10
365 return lights
366 elif offset==0x0e90: # Wind speed
367 return int(self.windSpeed)
368 elif offset==0x0e92: # Wind direction
369 return int(self.windDirection * 65536.0 / 360.0)
370 elif offset==0x11ba: # G-Load
371 return int(self.gLoad * 625.0)
372 elif offset==0x11c6: # Mach
373 # FIXME: calculate from IAS, altitude and QNH
374 return int(self.ias * 0.05 * 20480.)
375 elif offset==0x1244: # Centre 2 tank level
376 return self._getFuelLevel(self.FUEL_CENTRE_2)
377 elif offset==0x1248: # Centre 2 tank capacity
378 return self._getFuelCapacity(self.FUEL_CENTRE_2)
379 elif offset==0x1254: # External 1 tank level
380 return self._getFuelLevel(self.FUEL_EXTERNAL_1)
381 elif offset==0x1258: # External 1 tank capacity
382 return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
383 elif offset==0x125c: # External 2 tank level
384 return self._getFuelLevel(self.FUEL_EXTERNAL_2)
385 elif offset==0x1260: # External 2 tank capacity
386 return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
387 elif offset==0x2000: # Engine #1 N1
388 return self.n1[self.ENGINE_1]
389 elif offset==0x2100: # Engine #2 N1
390 return self.n1[self.ENGINE_2]
391 elif offset==0x2200: # Engine #3 N1
392 return self.n1[self.ENGINE_3]
393 elif offset==0x30c0: # Grossweight
394 return int((self.zfw + sum(self.fuelWeights)) * const.KGSTOLB)
395 elif offset==0x31e4: # Radio altitude
396 # FIXME: if self.radioAltitude is None, calculate from the
397 # altitude with some, perhaps random, ground altitude
398 # value
399 radioAltitude = (self.altitude - 517) \
400 if self.radioAltitude is None else self.radioAltitude
401 return (radioAltitude * const.FEETTOMETRES * 65536.0)
402 elif offset==0x3364: # Frozen
403 return 1 if self.frozen else 0
404 elif offset==0x3bfc: # ZFW
405 return int(self.zfw * 256.0 * const.KGSTOLB)
406 elif offset==0x3c00: # Path of the current AIR file
407 return self.airPath
408 elif offset==0x3d00: # Name of the current aircraft
409 return self.aircraftName
410 else:
411 print "Unhandled offset: %04x" % (offset,)
412 raise FSUIPCException(ERR_DATA)
413
414 def write(self, offset, value):
415 """Write the value at the given offset."""
416 try:
417 return self._write(offset, value)
418 except Exception, e:
419 print "failed to write offset %04x: %s" % (offset, str(e))
420 raise FSUIPCException(ERR_DATA)
421
422 def _write(self, offset, value):
423 """Write the given value at the given offset."""
424 if offset==0x023a: # Second of time
425 self._updateTimeOffset(5, value)
426 elif offset==0x023b: # Hour of Zulu time
427 self._updateTimeOffset(3, value)
428 elif offset==0x023c: # Minute of Zulu time
429 self._updateTimeOffset(4, value)
430 elif offset==0x023e: # Day number in the year
431 self._updateTimeOffset(7, value)
432 elif offset==0x0240: # Year in FS
433 self._updateTimeOffset(0, value)
434 elif offset==0x0264: # Paused
435 self.paused = value!=0
436 elif offset==0x029c: # Pitot
437 self.pitot = value!=0
438 elif offset==0x02b4: # Ground speed
439 # FIXME: calculate TAS using the heading and the wind, and
440 # then IAS based on the altitude
441 self.ias = value * 3600.0 / 65536.0 / 1852.0
442 elif offset==0x02bc: # IAS
443 self.ias = value / 128.0
444 elif offset==0x02c8: # VS
445 self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
446 elif offset==0x0330: # Altimeter
447 self.altimeter = value / 16.0
448 elif offset==0x0350: # NAV1
449 self.nav1 = Values._writeFrequency(value)
450 elif offset==0x0352: # NAV2
451 self.nav2 = Values._writeFrequency(value)
452 elif offset==0x0354: # Squawk
453 self.squawk = Values._writeBCD(value)
454 elif offset==0x0366: # Stalled
455 self.onTheGround = value!=0
456 elif offset==0x036c: # Stalled
457 self.stalled = value!=0
458 elif offset==0x036d: # Overspeed
459 self.overspeed = value!=0
460 elif offset==0x0570: # Altitude
461 self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
462 elif offset==0x0578: # Pitch
463 self.pitch = value * 360.0 / 65536.0 / 65536.0
464 elif offset==0x057c: # Bank
465 self.bank = value * 360.0 / 65536.0 / 65536.0
466 elif offset==0x0580: # Heading
467 self.heading = value * 360.0 / 65536.0 / 65536.0
468 elif offset==0x05dc: # Slew
469 self.slew = value!=0
470 elif offset==0x0628: # Replay
471 self.replay = value!=0
472 elif offset==0x088c: # Engine #1 throttle
473 self._setThrottle(self.ENGINE_1, value)
474 elif offset==0x0924: # Engine #2 throttle
475 self._setThrottle(self.ENGINE_2, value)
476 elif offset==0x09bc: # Engine #3 throttle
477 self._setThrottle(self.ENGINE_3, value)
478 elif offset==0x0af4: # Fuel weight
479 self.fuelWeight = value / 256.0
480 elif offset==0x0b74: # Centre tank level
481 self._setFuelLevel(self.FUEL_CENTRE, value)
482 elif offset==0x0b78: # Centre tank capacity
483 self._setFuelCapacity(self.FUEL_CENTRE, value)
484 elif offset==0x0b7c: # Left tank level
485 self._setFuelLevel(self.FUEL_LEFT, value)
486 elif offset==0x0b80: # Left tank capacity
487 self._setFuelCapacity(self.FUEL_LEFT, value)
488 elif offset==0x0b84: # Left aux tank level
489 self._setFuelLevel(self.FUEL_LEFT_AUX, value)
490 elif offset==0x0b88: # Left aux tank capacity
491 self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
492 elif offset==0x0b8c: # Left tip tank level
493 self._setFuelLevel(self.FUEL_LEFT_TIP, value)
494 elif offset==0x0b90: # Left tip tank capacity
495 self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
496 elif offset==0x0b94: # Right aux tank level
497 self._setFuelLevel(self.FUEL_RIGHT, value)
498 elif offset==0x0b98: # Right aux tank capacity
499 self._setFuelCapacity(self.FUEL_RIGHT, value)
500 elif offset==0x0b9c: # Right tank level
501 self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
502 elif offset==0x0ba0: # Right tank capacity
503 self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
504 elif offset==0x0ba4: # Right tip tank level
505 self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
506 elif offset==0x0ba8: # Right tip tank capacity
507 self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
508 elif offset==0x0bc8: # Parking
509 self.parking = value!=0
510 elif offset==0x0bcc: # Spoilers armed
511 self.spoilersArmed = value!=0
512 elif offset==0x0bd0: # Spoilers
513 self.spoilters = 0 if value==0 \
514 else (value - 4800) / (16383 - 4800)
515 elif offset==0x0bdc: # Flaps control
516 numNotchesM1 = len(self.flapsNotches) - 1
517 flapsIncrement = 16383.0 / numNotchesM1
518 index = value / flapsIncrement
519 if index>=numNotchesM1:
520 self.flapsControl = self.flapsNotches[-1]
521 else:
522 self.flapsControl = self.flapsNotches[index]
523 self.flapsControl += value * \
524 (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
525 flapsIncrement
526 elif offset==0x0be0 or offset==0x0be4: # Flaps left and right
527 self.flaps = value * self.flapsNotches[-1] / 16383.0
528 elif offset==0x0bec: # Nose gear
529 self.noseGear = value / 16383.0
530 elif offset==0x0d0c: # Lights
531 self.navLightsOn = (lights&0x01)!=0
532 self.antiCollisionLightsOn = (lights&0x02)!=0
533 self.landingLightsOn = (lights&0x04)!=0
534 self.strobeLightsOn = (lights&0x10)!=0
535 elif offset==0x0e90: # Wind speed
536 self.windSpeed = value
537 elif offset==0x0e92: # Wind direction
538 self.windDirection = value * 360.0 / 65536.0
539 elif offset==0x11ba: # G-Load
540 self.gLoad = value / 625.0
541 elif offset==0x11c6: # Mach
542 # FIXME: calculate IAS using the altitude and QNH
543 self.ias = value / 0.05 / 20480
544 elif offset==0x1244: # Centre 2 tank level
545 self._setFuelLevel(self.FUEL_CENTRE_2, value)
546 elif offset==0x1248: # Centre 2 tank capacity
547 self._setFuelCapacity(self.FUEL_CENTRE_2, value)
548 elif offset==0x1254: # External 1 tank level
549 self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
550 elif offset==0x1258: # External 1 tank capacity
551 self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
552 elif offset==0x125c: # External 2 tank level
553 self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
554 elif offset==0x1260: # External 2 tank capacity
555 self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
556 elif offset==0x2000: # Engine #1 N1
557 self.n1[self.ENGINE_1] = value
558 elif offset==0x2100: # Engine #2 N1
559 self.n1[self.ENGINE_2] = value
560 elif offset==0x2200: # Engine #3 N1
561 self.n1[self.ENGINE_3] = value
562 elif offset==0x30c0: # Grossweight
563 raise FSUIPCException(ERR_DATA)
564 elif offset==0x31e4: # Radio altitude
565 raise FSUIPCException(ERR_DATA)
566 elif offset==0x3364: # Frozen
567 self.frozen = value!=0
568 elif offset==0x3bfc: # ZFW
569 self.zfw = value * const.LBSTOKG / 256.0
570 print "ZFW:", self.zfw
571 elif offset==0x3c00: # Path of the current AIR file
572 self.airPath = value
573 elif offset==0x3d00: # Name of the current aircraft
574 self.aircraftName = value
575 else:
576 print "Unhandled offset: %04x" % (offset,)
577 raise FSUIPCException(ERR_DATA)
578
579 def _readUTC(self):
580 """Read the UTC time.
581
582 The current offset is added to it."""
583 return time.gmtime(time.time() + self._timeOffset)
584
585 def _getFuelLevel(self, index):
586 """Get the fuel level for the fuel tank with the given
587 index."""
588 return 0 if self.fuelCapacities[index]==0.0 else \
589 int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
590
591 def _getFuelCapacity(self, index):
592 """Get the capacity of the fuel tank with the given index."""
593 return int(self.fuelCapacities[index] * const.KGSTOLB / self.fuelWeight)
594
595 def _getThrottle(self, index):
596 """Get the throttle value for the given index."""
597 return int(self.throttles[index] * 16383.0)
598
599 def _updateTimeOffset(self, index, value):
600 """Update the time offset if the value in the tm structure is replaced
601 by the given value."""
602 tm = self._readUTC()
603 tm1 = tm[:index] + (value,) + tm[(index+1):]
604 self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
605
606 def _setThrottle(self, index, value):
607 """Set the throttle value for the given index."""
608 self.throttles[index] = value / 16383.0
609
610 def _setFuelLevel(self, index, value):
611 """Set the fuel level for the fuel tank with the given index."""
612 self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
613 65536.0 / 128.0
614
615 def _setFuelCapacity(self, index, value):
616 """Set the capacity of the fuel tank with the given index."""
617 self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
618
619
620#------------------------------------------------------------------------------
621
622values = Values()
623
624#------------------------------------------------------------------------------
625
626def open(request):
627 """Open the connection."""
628 return True
629
630#------------------------------------------------------------------------------
631
632def prepare_data(pattern, forRead = True):
633 """Prepare the given pattern for reading and/or writing."""
634 return pattern
635
636#------------------------------------------------------------------------------
637
638def read(data):
639 """Read the given data."""
640 return [values.read(offset) for (offset, type) in data]
641
642#------------------------------------------------------------------------------
643
644def write(data):
645 """Write the given data."""
646 for (offset, type, value) in data:
647 values.write(offset, value)
648
649#------------------------------------------------------------------------------
650
651def close():
652 """Close the connection."""
653 pass
654
655#------------------------------------------------------------------------------
656
657PORT=15015
658
659CALL_READ=1
660CALL_WRITE=2
661CALL_CLOSE=3
662
663RESULT_RETURNED=1
664RESULT_EXCEPTION=2
665
666#------------------------------------------------------------------------------
667
668class Server(threading.Thread):
669 """The server thread."""
670 def __init__(self):
671 """Construct the thread."""
672 super(Server, self).__init__()
673 self.daemon = True
674
675 def run(self):
676 """Perform the server's operation."""
677 serverSocket = socket.socket()
678
679 serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
680 serverSocket.bind(("", PORT))
681
682 serverSocket.listen(5)
683
684 while True:
685 (clientSocket, clientAddress) = serverSocket.accept()
686 thread = threading.Thread(target = self._process, args=(clientSocket,))
687 thread.start()
688
689 def _process(self, clientSocket):
690 """Process the commands arriving on the given socket."""
691 socketFile = clientSocket.makefile()
692 try:
693 while True:
694 (length,) = struct.unpack("I", clientSocket.recv(4))
695 data = clientSocket.recv(length)
696 (call, args) = cPickle.loads(data)
697 exception = None
698
699 try:
700 if call==CALL_READ:
701 result = read(args[0])
702 elif call==CALL_WRITE:
703 result = write(args[0])
704 else:
705 break
706 except Exception, e:
707 exception = e
708
709 if exception is None:
710 data = cPickle.dumps((RESULT_RETURNED, result))
711 else:
712 data = cPickle.dumps((RESULT_EXCEPTION, exception))
713 clientSocket.send(struct.pack("I", len(data)) + data)
714 except Exception, e:
715 print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
716 finally:
717 try:
718 socketFile.close()
719 except:
720 pass
721 clientSocket.close()
722
723#------------------------------------------------------------------------------
724
725class Client(object):
726 """Client to the server."""
727 def __init__(self, serverHost):
728 """Construct the client and connect to the given server
729 host."""
730 self._socket = socket.socket()
731 self._socket.connect((serverHost, PORT))
732
733 self._socketFile = self._socket.makefile()
734
735 def read(self, data):
736 """Read the given data."""
737 return self._call(CALL_READ, data)
738
739 def write(self, data):
740 """Write the given data."""
741 return self._call(CALL_WRITE, data)
742
743 def _call(self, command, data):
744 """Perform a call with the given command and data."""
745 data = cPickle.dumps((command, [data]))
746 self._socket.send(struct.pack("I", len(data)) + data)
747 (length,) = struct.unpack("I", self._socket.recv(4))
748 data = self._socket.recv(length)
749 (resultCode, result) = cPickle.loads(data)
750 if resultCode==RESULT_RETURNED:
751 return result
752 else:
753 raise result
754
755#------------------------------------------------------------------------------
756#------------------------------------------------------------------------------
757
758# FIXME: implement proper completion and history
759class CLI(threading.Thread, cmd.Cmd):
760 """The command-line interpreter."""
761 def __init__(self, clientSocket):
762 """Construct the CLI."""
763 self._socket = clientSocket
764 self._socketFile = clientSocket.makefile("rwb")
765
766 threading.Thread.__init__(self)
767 cmd.Cmd.__init__(self,
768 stdin = self._socketFile, stdout = self._socketFile)
769
770 self.use_rawinput = False
771 self.intro = "\nPyUIPC simulator command prompt\n"
772 self.prompt = "PyUIPC> "
773
774 self.daemon = True
775
776 def run(self):
777 """Execute the thread."""
778 try:
779 self.cmdloop()
780 except Exception, e:
781 print "pyuipc_sim.CLI.run: command loop terminated abnormally: " + str(e)
782
783 try:
784 self._socketFile.close()
785 except:
786 pass
787
788 self._socket.close()
789
790 def do_set(self, args):
791 """Handle the set command."""
792 words = args.split()
793 if len(words)==2:
794 variable = words[0]
795 if variable in ["zfw"]:
796 values.__setattr__(variable, float(words[1]))
797 else:
798 print >> self._socketFile, "Unhandled variable: " + variable
799 return False
800
801 def help_set(self):
802 """Print help for the set command."""
803 print >> self._socketFile, "set <variable> <value>"
804
805 def do_quit(self, args):
806 """Handle the quit command."""
807 return True
808
809#------------------------------------------------------------------------------
810
811if __name__ == "__main__":
812 client = Client("127.0.0.1")
813 # print client.write([(0x3bfc, "d", 1000 * 256.0 * const.KGSTOLB)])
814 # print client.read([(0x3bfc, "d")])
815 print client.write([(0x023c, "d", 30)])
816 print client.read([(0x023c, "d")])
817else:
818 server = Server()
819 server.start()
820
821#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.