source: xplra/src/client/python/xplra.py@ 35:9451e75788ea

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

Implemented support for multi-dataref setting

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