source: xplra/src/client/python/xplra.py@ 37:0576c4bf175b

Last change on this file since 37:0576c4bf175b was 37:0576c4bf175b, checked in by István Váradi <ivaradi@…>, 11 years ago

Fixed call

File size: 21.6 KB
Line 
1# X-Plane Remote Access client module
2#-------------------------------------------------------------------------------
3
4import os
5import struct
6import array
7
8#-------------------------------------------------------------------------------
9
10COMMAND_GET_SINGLE = 0x01
11
12COMMAND_SET_SINGLE = 0x02
13
14COMMAND_GET_MULTI = 0x03
15
16COMMAND_SET_MULTI = 0x04
17
18COMMAND_REGISTER_GET_MULTI = 0x11
19
20COMMAND_UNREGISTER_GET_MULTI = 0x12
21
22COMMAND_EXECUTE_GET_MULTI = 0x13
23
24COMMAND_REGISTER_SET_MULTI = 0x21
25
26COMMAND_UNREGISTER_SET_MULTI = 0x22
27
28COMMAND_EXECUTE_SET_MULTI = 0x23
29
30COMMAND_GET_VERSIONS = 0x31
31
32TYPE_INT = 0x01
33
34TYPE_FLOAT = 0x02
35
36TYPE_DOUBLE = 0x03
37
38TYPE_FLOAT_ARRAY = 0x11
39
40TYPE_INT_ARRAY = 0x12
41
42TYPE_BYTE_ARRAY = 0x13
43
44RESULT_OK = 0x00
45
46RESULT_INVALID_COMMAND = 0x01
47
48RESULT_UNKNOWN_DATAREF = 0x02
49
50RESULT_INVALID_TYPE = 0x03
51
52RESULT_INVALID_LENGTH = 0x04
53
54RESULT_INVALID_OFFSET = 0x05
55
56RESULT_INVALID_COUNT = 0x06
57
58RESULT_INVALID_ID = 0x07
59
60RESULT_OTHER_ERROR = 0xff
61
62#-------------------------------------------------------------------------------
63
64class ProtocolException(Exception):
65 """Exception to signify protocol errors."""
66 _message = { RESULT_INVALID_COMMAND : "invalid command",
67 RESULT_UNKNOWN_DATAREF : "unknown dataref",
68 RESULT_INVALID_TYPE : "invalid type",
69 RESULT_INVALID_LENGTH : "invalid length",
70 RESULT_INVALID_OFFSET : "invalid offset",
71 RESULT_INVALID_COUNT : "invalid count",
72 RESULT_INVALID_ID : "invalid ID",
73 RESULT_OTHER_ERROR : "other error" }
74
75 @staticmethod
76 def getMessage(resultCode):
77 """Get the message for the given result code."""
78 if resultCode in ProtocolException._message:
79 return ProtocolException._message[resultCode]
80 else:
81 return "unknown error code " + `resultCode`
82
83 def __init__(self, resultCode):
84 super(ProtocolException, self).__init__("xplra.ProtocolException: " +
85 self.getMessage(resultCode))
86 self.resultCode = resultCode
87
88#-------------------------------------------------------------------------------
89
90class XPlane(object):
91 """The main class representing the connection to X-Plane."""
92
93 @staticmethod
94 def _packLength(x):
95 """Pack the given integer as a variable-length value."""
96 s = ""
97 while True:
98 y = x&0x7f
99 x >>= 7
100 if x>0: y |= 0x80
101 s += struct.pack("B", y)
102 if x==0:
103 return s
104
105 @staticmethod
106 def _packString(s):
107 """Pack the given string."""
108 return XPlane._packLength(len(s)) + s
109
110 def __init__(self):
111 """Construct the object."""
112 self._stream = None
113
114 def connect(self):
115 """Try to connect to X-Plane."""
116 if self._stream is not None:
117 return
118
119 if os.name=="nt":
120 self._stream = open(r'\\.\pipe\\xplra', "b")
121 else:
122 import socket
123 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
124 s.connect("/tmp/xplra-" + os.environ["LOGNAME"])
125 self._stream = s.makefile()
126
127 @property
128 def isConnected(self):
129 """Determine if we are connected to the simulator."""
130 return self._stream is not None
131
132 def createMultiGetter(self):
133 """Create a new multi-dataref getter for this X-Plane object."""
134 return MultiGetter(self)
135
136 def createMultiSetter(self):
137 """Create a new multi-dataref setter for this X-Plane object."""
138 return MultiSetter(self)
139
140 def getVersions(self):
141 """Get the versions of X-Plane, XPLM and XPLRA as a tuple."""
142 self._writeU8(COMMAND_GET_VERSIONS)
143 self._flush()
144 self._checkResult()
145 return (self._readS32(), self._readS32(), self._readS32())
146
147 def getInt(self, name):
148 """Get the value of the integer dataref with the given name."""
149 return self._getSingle(name, TYPE_INT)
150
151 def getFloat(self, name):
152 """Get the value of the float dataref with the given name."""
153 return self._getSingle(name, TYPE_FLOAT)
154
155 def getDouble(self, name):
156 """Get the value of the double dataref with the given name."""
157 return self._getSingle(name, TYPE_DOUBLE)
158
159 def getFloatArray(self, name, length = -1, offset = 0):
160 """Get the value of the float array dataref with the given name."""
161 return self._getSingle(name, TYPE_FLOAT_ARRAY, length, offset)
162
163 def getIntArray(self, name, length = -1, offset = 0):
164 """Get the value of the integer array dataref with the given name."""
165 return self._getSingle(name, TYPE_INT_ARRAY, length, offset)
166
167 def getByteArray(self, name, length = -1, offset = 0):
168 """Get the value of the byte array dataref with the given name."""
169 return self._getSingle(name, TYPE_BYTE_ARRAY, length, offset)
170
171 def getString(self, name, offset = 0):
172 """Get the value of the byte array dataref with the given name as a
173 string."""
174 s = ""
175 for b in self.getByteArray(name, offset = offset):
176 if b==0: break
177 s += chr(b)
178 return s
179
180 def setInt(self, name, value):
181 """Set the value of the integer dataref with the given name."""
182 self._setSingle(name, TYPE_INT, value)
183
184 def setFloat(self, name, value):
185 """Set the value of the float dataref with the given name."""
186 self._setSingle(name, TYPE_FLOAT, value)
187
188 def setDouble(self, name, value):
189 """Set the value of the double dataref with the given name."""
190 self._setSingle(name, TYPE_DOUBLE, value)
191
192 def setFloatArray(self, name, value, offset = 0):
193 """Set the value of the float array dataref with the given name."""
194 self._setSingle(name, TYPE_FLOAT_ARRAY, value, offset = offset)
195
196 def setIntArray(self, name, value, offset = 0):
197 """Set the value of the integer array dataref with the given name."""
198 self._setSingle(name, TYPE_INT_ARRAY, value, offset = offset)
199
200 def setByteArray(self, name, value, offset = 0):
201 """Set the value of the byte array dataref with the given name."""
202 self._setSingle(name, TYPE_BYTE_ARRAY, value, offset = offset)
203
204 def setString(self, name, value, length, offset = 0):
205 """Set the value of the byte array dataref with the given name from a
206 string.
207
208 The string will be padded with 0's so that its length is the given
209 one."""
210 value = [ord(c) for c in value[:length]]
211 value += [0] * (length - len(value))
212 self.setByteArray(name, value, offset)
213
214 def disconnect(self):
215 """Disconnect from X-Plane."""
216 if self._stream is not None:
217 self._stream.close()
218 self._stream = None
219
220 def _checkResult(self, resultCode = None):
221 """Check the given result code.
222
223 If it is None, it will be read first.
224
225 If it is not RESULT_OK, a ProtocolException is thrown."""
226
227 if resultCode is None:
228 resultCode = self._readU8()
229 if resultCode!=RESULT_OK:
230 raise ProtocolException(resultCode)
231
232 def _getSingle(self, name, type, length = None, offset = None):
233 """Get the single value of the given name and type."""
234 self._writeU8(COMMAND_GET_SINGLE)
235 self._writeString(name)
236 self._writeU8(type)
237 if length is not None and offset is not None:
238 self._writeS32(length)
239 self._writeS32(offset)
240
241 self._flush()
242 self._checkResult()
243 return self._readValue(type)
244
245 def _readValue(self, type):
246 """Read the value of the given type from the stream."""
247 if type==TYPE_INT:
248 return self._readS32()
249 elif type==TYPE_FLOAT:
250 return self._readFloat()
251 elif type==TYPE_DOUBLE:
252 return self._readDouble()
253 else:
254 length = self._readS32()
255 arr = None
256 elementSize = 4
257 if type==TYPE_FLOAT_ARRAY:
258 arr = array.array("f")
259 elif type==TYPE_INT_ARRAY:
260 arr = array.array("i")
261 elif type==TYPE_BYTE_ARRAY:
262 arr = array.array("B")
263 elementSize = 1
264 if arr is not None and length>0:
265 arr.fromstring(self._stream.read(length*elementSize))
266 return None if arr is None else arr.tolist()
267
268 def _setSingle(self, name, type, value, offset = None):
269 """Set the single value of the given name and type."""
270 self._writeU8(COMMAND_SET_SINGLE)
271 self._writeString(name)
272 self._writeU8(type)
273 if offset is not None:
274 self._writeS32(len(value))
275 self._writeS32(offset)
276
277 self._writeValue(type, value)
278 self._flush()
279 self._checkResult()
280
281 def _writeValue(self, type, value):
282 """Write a value of the given type."""
283 if type==TYPE_INT:
284 self._writeS32(int(value))
285 elif type==TYPE_FLOAT:
286 self._writeFloat(float(value))
287 elif type==TYPE_DOUBLE:
288 self._writeDouble(float(value))
289 else:
290 arr = None
291 if type==TYPE_FLOAT_ARRAY:
292 arr = array.array("f")
293 elif type==TYPE_INT_ARRAY:
294 arr = array.array("i")
295 elif type==TYPE_BYTE_ARRAY:
296 arr = array.array("B")
297 arr.fromlist(value)
298 self._stream.write(arr.tostring())
299
300 def _writeU8(self, x):
301 """Write the given value as an unsigned, 8-bit value."""
302 self._stream.write(struct.pack("B", x))
303
304 def _writeS32(self, x):
305 """Write the given value as a signed, 32-bit value."""
306 self._stream.write(struct.pack("i", x))
307
308 def _writeU32(self, x):
309 """Write the given value as an unsigned, 32-bit value."""
310 self._stream.write(struct.pack("I", x))
311
312 def _writeFloat(self, x):
313 """Write the given value as a single-precision floating point."""
314 self._stream.write(struct.pack("f", x))
315
316 def _writeDouble(self, x):
317 """Write the given value as a double-precision floating point."""
318 self._stream.write(struct.pack("d", x))
319
320 def _writeLength(self, length):
321 """Write the given value is a variable-length value into our stream."""
322 self._stream.write(self._packLength(length))
323
324 def _writeString(self, str):
325 """Write the given string into the stream."""
326 self._stream.write(self._packString(str))
327
328 def _flush(self):
329 """Flush our stream."""
330 self._stream.flush()
331
332 def _readU8(self):
333 """Read an unsigned, 8-bit value from the stream."""
334 (value,) = struct.unpack("B", self._stream.read(1))
335 return value
336
337 def _readS32(self):
338 """Read a signed, 32-bit value from the stream."""
339 (value,) = struct.unpack("i", self._stream.read(4))
340 return value
341
342 def _readU32(self):
343 """Read an unsigned, 32-bit value from the stream."""
344 (value,) = struct.unpack("I", self._stream.read(4))
345 return value
346
347 def _readFloat(self):
348 """Read a single-precision floating point value from the stream."""
349 (value,) = struct.unpack("f", self._stream.read(4))
350 return value
351
352 def _readDouble(self):
353 """Read a double-precision floating point value from the stream."""
354 (value,) = struct.unpack("d", self._stream.read(8))
355 return value
356
357 def _readLength(self):
358 """Read a variable-length value from our stream."""
359 length = 0
360 while True:
361 (x,) = struct.unpack("B", self._stream.read(1))
362 length <<= 7
363 length |= x&0x7f
364 if x&0x80==0:
365 return length
366
367 def _readString(self):
368 """Read a string from our stream."""
369 length = self._readLength()
370 return self._stream.read(length)
371
372#-------------------------------------------------------------------------------
373
374class MultiBuffer(object):
375 """Buffer for querying or setting multi-dataref values."""
376 @staticmethod
377 def _getDefault(type, length):
378 """Get the default value for the given type."""
379 if type==TYPE_INT:
380 return 0
381 elif type==TYPE_FLOAT:
382 return float(0.0)
383 elif type==TYPE_DOUBLE:
384 return 0.0
385 elif length<=0:
386 return []
387 elif type==TYPE_FLOAT_ARRAY:
388 return [float(0.0)] * length
389 elif type==TYPE_INT_ARRAY or type==TYPE_BYTE_ARRAY:
390 return [0] * length
391
392 def __init__(self, xplane, registerCommand, unregisterCommand):
393 """Construct the buffer for the given XPlane instance and with the
394 given register/unregister command values."""
395 self._xplane = xplane
396 self._registerCommand = registerCommand
397 self._unregisterCommand = unregisterCommand
398
399 self._dataRefs = []
400 self._values = None
401
402 self._registeredID = None
403
404 @property
405 def values(self):
406 """Query the values as a list."""
407 if self._values is None:
408 self.finalize()
409 return self._values
410
411 def addInt(self, name):
412 """Add an integer to the buffer with the given name
413
414 Returns an ID (or index) of the dataref."""
415 return self._add(name, TYPE_INT)
416
417 def addFloat(self, name):
418 """Add a float to the buffer with the given name
419
420 Returns an ID (or index) of the dataref."""
421 return self._add(name, TYPE_FLOAT)
422
423 def addDouble(self, name):
424 """Add a double to the buffer with the given name
425
426 Returns an ID (or index) of the dataref."""
427 return self._add(name, TYPE_DOUBLE)
428
429 def addFloatArray(self, name, length = -1, offset = 0):
430 """Add a floating point array to the buffer with the given name.
431
432 Returns an ID (or index) of the dataref."""
433 return self._add(name, TYPE_FLOAT_ARRAY, length, offset)
434
435 def addIntArray(self, name, length = -1, offset = 0):
436 """Add an integer array to the buffer with the given name.
437
438 Returns an ID (or index) of the dataref."""
439 return self._add(name, TYPE_INT_ARRAY, length, offset)
440
441 def addByteArray(self, name, length = -1, offset = 0):
442 """Add a byte array to the buffer with the given name.
443
444 Returns an ID (or index) of the dataref."""
445 return self._add(name, TYPE_BYTE_ARRAY, length, offset)
446
447 def finalize(self):
448 """Finalize the buffer, if not finalized yet.
449
450 It initializes the array of values with some defaults.
451
452 Returns whether there is any data in the buffer."""
453 if self._values is None:
454 self._values = [self._getDefault(type, length)
455 for (_, type, length, _) in self._dataRefs]
456 return self._values!=[]
457
458 def register(self):
459 """Register the buffer in X-Plane."""
460 if self.finalize() and self._registeredID is None:
461 self._writeSpec(self._registerCommand)
462 self._registeredID = self._xplane._readU32()
463
464 def unregister(self):
465 """Unregister the buffer from X-Plane."""
466 if self._registeredID is not None:
467 self._xplane._writeU8(self._unregisterCommand)
468 self._xplane._writeU32(self._registeredID)
469 self._xplane._flush()
470 self._xplane._checkResult()
471 self._registeredID = None
472
473 def execute(self):
474 """Perform the querying or the setting of the values.
475
476 It first checks if the buffer is finalized. If not, it will be
477 finalized. However, if it is not finalized but also registered, it will
478 first be unregistered and the re-registered after finalizing."""
479 if self._values is None and self._registeredID is not None:
480 self.unregister()
481 self.register()
482 else:
483 self.finalize()
484
485 if self._registeredID is None:
486 self._executeUnregistered()
487 else:
488 self._executeRegistered()
489
490 def getString(self, id):
491 """Get the value of the dataref with the given ID as a string.
492
493 The dataref should be of type byte array."""
494 if self._dataRefs[id][1]!=TYPE_BYTE_ARRAY:
495 raise TypeError("xplra.MultiBuffer.getString: only byte arrays can be converted to strings")
496 if self._values is None:
497 self.finalize()
498 s = ""
499 for c in self._values[id]:
500 if c==0: break
501 s += chr(c)
502 return s
503
504 def _add(self, name, type, length = None, offset = None):
505 """Add a scalar to the buffer with the given name and type"""
506 index = len(self._dataRefs)
507 self._values = None
508 self._dataRefs.append( (name, type, length, offset) )
509 return index
510
511 def _writeSpec(self, command):
512 """Write the specification preceded by the given command and check for
513 the result.
514
515 The specification is basically the list of the datarefs."""
516 self._xplane._writeU8(command)
517 self._xplane._writeU32(len(self._dataRefs))
518
519 for (name, type, length, offset) in self._dataRefs:
520 self._xplane._writeString(name)
521 self._xplane._writeU8(type)
522 if length is not None and offset is not None:
523 self._xplane._writeS32(length)
524 self._xplane._writeS32(offset)
525
526 self._xplane._flush()
527 self._xplane._checkResult()
528
529 def __len__(self):
530 """Get the number of value items in the buffer."""
531 if self._values is None:
532 self.finalize()
533 return len(self._values)
534
535 def __getitem__(self, id):
536 """Get the item with the given ID."""
537 if self._values is None:
538 self.finalize()
539 return self._values[id]
540
541 def __setitem__(self, id, value):
542 """Set the item with the given ID."""
543 if self._values is None:
544 self.finalize()
545 type = self._dataRefs[id][1]
546 length = self._dataRefs[id][2]
547 if type==TYPE_INT:
548 self._values[id] = int(value)
549 elif type==TYPE_FLOAT or type==TYPE_DOUBLE:
550 self._values[id] = float(value)
551 elif type==TYPE_FLOAT_ARRAY:
552 if len(value)!=length:
553 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
554 self._values[id] = [float(x) for x in value]
555 elif type==TYPE_INT_ARRAY:
556 if len(value)!=length:
557 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
558 self._values[id] = [int(x) for x in value]
559 elif type==TYPE_BYTE_ARRAY:
560 if isinstance(value, str):
561 lst = [ord(x) for x in value[:length]]
562 if len(lst)<length:
563 lst += [0] * (length-len(lst))
564 self._values[id] = lst
565 else:
566 if len(value)!=length:
567 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
568 self._values[id] = [int(x) for x in value]
569
570 def __iter__(self):
571 """Get an iterator over the values of this buffer."""
572 if self._values is None:
573 self.finalize()
574 return iter(self._values)
575
576
577#-------------------------------------------------------------------------------
578
579class MultiGetter(MultiBuffer):
580 """Multi-dataref buffer for querying."""
581 def __init__(self, xplane):
582 """Construct the getter."""
583 super(MultiGetter, self).__init__(xplane,
584 COMMAND_REGISTER_GET_MULTI,
585 COMMAND_UNREGISTER_GET_MULTI)
586
587 def _executeRegistered(self):
588 """Execute the query if the buffer is registered."""
589 self._xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
590 self._xplane._writeU32(self._registeredID)
591 self._xplane._flush()
592
593 self._xplane._checkResult()
594
595 self._readValues()
596
597 def _executeUnregistered(self):
598 """Execute the query if the buffer is not registered."""
599 self._writeSpec(COMMAND_GET_MULTI)
600 self._readValues()
601
602 def _readValues(self):
603 """Read the values into the values array."""
604 for i in range(0, len(self._dataRefs)):
605 self._values[i] = self._xplane._readValue(self._dataRefs[i][1])
606
607#-------------------------------------------------------------------------------
608
609class MultiSetter(MultiBuffer):
610 """Multi-dataref buffer for setting."""
611 def __init__(self, xplane):
612 """Construct the getter."""
613 super(MultiSetter, self).__init__(xplane,
614 COMMAND_REGISTER_SET_MULTI,
615 COMMAND_UNREGISTER_SET_MULTI)
616
617 def _executeRegistered(self):
618 """Execute the query if the buffer is registered."""
619 self._xplane._writeU8(COMMAND_EXECUTE_SET_MULTI)
620 self._xplane._writeU32(self._registeredID)
621 for i in range(0, len(self._dataRefs)):
622 self._xplane._writeValue(self._dataRefs[i][1], self._values[i])
623 self._xplane._flush()
624
625 self._xplane._checkResult()
626
627 def _executeUnregistered(self):
628 """Execute the query if the buffer is not registered."""
629 self._xplane._writeU8(COMMAND_SET_MULTI)
630
631 numDataRefs = len(self._dataRefs)
632 self._xplane._writeU32(numDataRefs)
633
634 for i in range(0, numDataRefs):
635 (name, type, length, offset) = self._dataRefs[i]
636 value = self._values[i]
637
638 self._xplane._writeString(name)
639 self._xplane._writeU8(type)
640 if length is not None and offset is not None:
641 self._xplane._writeS32(len(value))
642 self._xplane._writeS32(offset)
643 self._xplane._writeValue(type, value)
644
645 self._xplane._flush()
646 self._xplane._checkResult()
647
648#-------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.