source: xplra/src/client/python/xplra.py@ 34:3a90641eba91

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

Implemented the multi-dataref getter in Python

File size: 19.0 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 getInt(self, name):
135 """Get the value of the integer dataref with the given name."""
136 return self._getSingle(name, TYPE_INT)
137
138 def getFloat(self, name):
139 """Get the value of the float dataref with the given name."""
140 return self._getSingle(name, TYPE_FLOAT)
141
142 def getDouble(self, name):
143 """Get the value of the double dataref with the given name."""
144 return self._getSingle(name, TYPE_DOUBLE)
145
146 def getFloatArray(self, name, length = -1, offset = 0):
147 """Get the value of the float array dataref with the given name."""
148 return self._getSingle(name, TYPE_FLOAT_ARRAY, length, offset)
149
150 def getIntArray(self, name, length = -1, offset = 0):
151 """Get the value of the integer array dataref with the given name."""
152 return self._getSingle(name, TYPE_INT_ARRAY, length, offset)
153
154 def getByteArray(self, name, length = -1, offset = 0):
155 """Get the value of the byte array dataref with the given name."""
156 return self._getSingle(name, TYPE_BYTE_ARRAY, length, offset)
157
158 def getString(self, name, offset = 0):
159 """Get the value of the byte array dataref with the given name as a
160 string."""
161 s = ""
162 for b in self.getByteArray(name, offset = offset):
163 if b==0: break
164 s += chr(b)
165 return s
166
167 def setInt(self, name, value):
168 """Set the value of the integer dataref with the given name."""
169 self._setSingle(name, TYPE_INT, value)
170
171 def setFloat(self, name, value):
172 """Set the value of the float dataref with the given name."""
173 self._setSingle(name, TYPE_FLOAT, value)
174
175 def setDouble(self, name, value):
176 """Set the value of the double dataref with the given name."""
177 self._setSingle(name, TYPE_DOUBLE, value)
178
179 def setFloatArray(self, name, value, offset = 0):
180 """Set the value of the float array dataref with the given name."""
181 self._setSingle(name, TYPE_FLOAT_ARRAY, value, offset = offset)
182
183 def setIntArray(self, name, value, offset = 0):
184 """Set the value of the integer array dataref with the given name."""
185 self._setSingle(name, TYPE_INT_ARRAY, value, offset = offset)
186
187 def setByteArray(self, name, value, offset = 0):
188 """Set the value of the byte array dataref with the given name."""
189 self._setSingle(name, TYPE_BYTE_ARRAY, value, offset = offset)
190
191 def setString(self, name, value, length, offset = 0):
192 """Set the value of the byte array dataref with the given name from a
193 string.
194
195 The string will be padded with 0's so that its length is the given
196 one."""
197 value = [ord(c) for c in value[:length]]
198 value += [0] * (length - len(value))
199 self.setByteArray(name, value, offset)
200
201 def disconnect(self):
202 """Disconnect from X-Plane."""
203 if self._stream is not None:
204 self._stream.close()
205 self._stream = None
206
207 def _checkResult(self, resultCode = None):
208 """Check the given result code.
209
210 If it is None, it will be read first.
211
212 If it is not RESULT_OK, a ProtocolException is thrown."""
213
214 if resultCode is None:
215 resultCode = self._readU8()
216 if resultCode!=RESULT_OK:
217 raise ProtocolException(resultCode)
218
219 def _getSingle(self, name, type, length = None, offset = None):
220 """Get the single value of the given name and type."""
221 self._writeU8(COMMAND_GET_SINGLE)
222 self._writeString(name)
223 self._writeU8(type)
224 if length is not None and offset is not None:
225 self._writeS32(length)
226 self._writeS32(offset)
227
228 self._flush()
229 self._checkResult()
230 return self._readValue(type)
231
232 def _readValue(self, type):
233 """Read the value of the given type from the stream."""
234 if type==TYPE_INT:
235 return self._readS32()
236 elif type==TYPE_FLOAT:
237 return self._readFloat()
238 elif type==TYPE_DOUBLE:
239 return self._readDouble()
240 else:
241 length = self._readS32()
242 arr = None
243 elementSize = 4
244 if type==TYPE_FLOAT_ARRAY:
245 arr = array.array("f")
246 elif type==TYPE_INT_ARRAY:
247 arr = array.array("i")
248 elif type==TYPE_BYTE_ARRAY:
249 arr = array.array("B")
250 elementSize = 1
251 if arr is not None and length>0:
252 arr.fromstring(self._stream.read(length*elementSize))
253 return arr
254
255 def _setSingle(self, name, type, value, offset = None):
256 """Set the single value of the given name and type."""
257 self._writeU8(COMMAND_SET_SINGLE)
258 self._writeString(name)
259 self._writeU8(type)
260 if offset is not None:
261 self._writeS32(len(value))
262 self._writeS32(offset)
263
264 self._writeValue(type, value)
265 self._flush()
266 self._checkResult()
267
268 def _writeValue(self, type, value):
269 """Write a value of the given type."""
270 if type==TYPE_INT:
271 self._writeS32(int(value))
272 elif type==TYPE_FLOAT:
273 self._writeFloat(float(value))
274 elif type==TYPE_DOUBLE:
275 self._writeDouble(float(value))
276 else:
277 arr = None
278 if type==TYPE_FLOAT_ARRAY:
279 arr = array.array("f")
280 elif type==TYPE_INT_ARRAY:
281 arr = array.array("i")
282 elif type==TYPE_BYTE_ARRAY:
283 arr = array.array("B")
284 arr.fromlist(value)
285 self._stream.write(arr.tostring())
286
287 def _writeU8(self, x):
288 """Write the given value as an unsigned, 8-bit value."""
289 self._stream.write(struct.pack("B", x))
290
291 def _writeS32(self, x):
292 """Write the given value as a signed, 32-bit value."""
293 self._stream.write(struct.pack("i", x))
294
295 def _writeU32(self, x):
296 """Write the given value as an unsigned, 32-bit value."""
297 self._stream.write(struct.pack("I", x))
298
299 def _writeFloat(self, x):
300 """Write the given value as a single-precision floating point."""
301 self._stream.write(struct.pack("f", x))
302
303 def _writeDouble(self, x):
304 """Write the given value as a double-precision floating point."""
305 self._stream.write(struct.pack("d", x))
306
307 def _writeLength(self, length):
308 """Write the given value is a variable-length value into our stream."""
309 self._stream.write(self._packLength(length))
310
311 def _writeString(self, str):
312 """Write the given string into the stream."""
313 self._stream.write(self._packString(str))
314
315 def _flush(self):
316 """Flush our stream."""
317 self._stream.flush()
318
319 def _readU8(self):
320 """Read an unsigned, 8-bit value from the stream."""
321 (value,) = struct.unpack("B", self._stream.read(1))
322 return value
323
324 def _readS32(self):
325 """Read a signed, 32-bit value from the stream."""
326 (value,) = struct.unpack("i", self._stream.read(4))
327 return value
328
329 def _readU32(self):
330 """Read an unsigned, 32-bit value from the stream."""
331 (value,) = struct.unpack("I", self._stream.read(4))
332 return value
333
334 def _readFloat(self):
335 """Read a single-precision floating point value from the stream."""
336 (value,) = struct.unpack("f", self._stream.read(4))
337 return value
338
339 def _readDouble(self):
340 """Read a double-precision floating point value from the stream."""
341 (value,) = struct.unpack("d", self._stream.read(8))
342 return value
343
344 def _readLength(self):
345 """Read a variable-length value from our stream."""
346 length = 0
347 while True:
348 (x,) = struct.unpack("B", self._stream.read(1))
349 length <<= 7
350 length |= x&0x7f
351 if x&0x80==0:
352 return length
353
354 def _readString(self):
355 """Read a string from our stream."""
356 length = self._readLength()
357 return self._stream.read(length)
358
359#-------------------------------------------------------------------------------
360
361class MultiBuffer(object):
362 """Buffer for querying or setting multi-dataref values."""
363 @staticmethod
364 def _getDefault(type, length):
365 """Get the default value for the given type."""
366 if type==TYPE_INT:
367 return 0
368 elif type==TYPE_FLOAT:
369 return float(0.0)
370 elif type==TYPE_DOUBLE:
371 return 0.0
372 elif length<=0:
373 return []
374 elif type==TYPE_FLOAT_ARRAY:
375 return [float(0.0)] * length
376 elif type==TYPE_INT_ARRAY or type==TYPE_BYTE_ARRAY:
377 return [0] * length
378
379 def __init__(self, xplane, registerCommand, unregisterCommand):
380 """Construct the buffer for the given XPlane instance and with the
381 given register/unregister command values."""
382 self._xplane = xplane
383 self._registerCommand = registerCommand
384 self._unregisterCommand = unregisterCommand
385
386 self._dataRefs = []
387 self._values = None
388
389 self._registeredID = None
390
391 def addInt(self, name):
392 """Add an integer to the buffer with the given name
393
394 Returns an ID (or index) of the dataref."""
395 return self._add(name, TYPE_INT)
396
397 def addFloat(self, name):
398 """Add a float to the buffer with the given name
399
400 Returns an ID (or index) of the dataref."""
401 return self._add(name, TYPE_FLOAT)
402
403 def addDouble(self, name):
404 """Add a double to the buffer with the given name
405
406 Returns an ID (or index) of the dataref."""
407 return self._add(name, TYPE_DOUBLE)
408
409 def addFloatArray(self, name, length = -1, offset = 0):
410 """Add a floating point array to the buffer with the given name.
411
412 Returns an ID (or index) of the dataref."""
413 return self._add(name, TYPE_FLOAT_ARRAY, length, offset)
414
415 def addIntArray(self, name, length = -1, offset = 0):
416 """Add an integer array to the buffer with the given name.
417
418 Returns an ID (or index) of the dataref."""
419 return self._add(name, TYPE_INT_ARRAY, length, offset)
420
421 def addByteArray(self, name, length = -1, offset = 0):
422 """Add a byte array to the buffer with the given name.
423
424 Returns an ID (or index) of the dataref."""
425 return self._add(name, TYPE_BYTE_ARRAY, length, offset)
426
427 def finalize(self):
428 """Finalize the buffer, if not finalized yet.
429
430 It initializes the array of values with some defaults.
431
432 Returns whether there is any data in the buffer."""
433 if self._values is None:
434 self._values = [self._getDefault(type, length)
435 for (_, type, length, _) in self._dataRefs]
436 return self._values!=[]
437
438 def register(self):
439 """Register the buffer in X-Plane."""
440 if self.finalize() and self._registeredID is None:
441 self._writeSpec(self._registerCommand)
442 self._registeredID = self._xplane._readU32()
443
444 def unregister(self):
445 """Unregister the buffer from X-Plane."""
446 if self._registeredID is not None:
447 self._xplane._writeU8(self._unregisterCommand)
448 self._xplane._writeU32(self._registeredID)
449 self._xplane._flush()
450 self._xplane._checkResult()
451 self._registeredID = None
452
453 def execute(self):
454 """Perform the querying or the setting of the values.
455
456 It first checks if the buffer is finalized. If not, it will be
457 finalized. However, if it is not finalized but also registered, it will
458 first be unregistered and the re-registered after finalizing."""
459 if self._values is None and self._registeredID is not None:
460 self.unregister()
461 self.register()
462 else:
463 self.finalize()
464
465 if self._registeredID is None:
466 self._executeUnregistered()
467 else:
468 self._executeRegistered()
469
470 def getString(self, id):
471 """Get the value of the dataref with the given ID as a string.
472
473 The dataref should be of type byte array."""
474 if self._dataRefs[id][1]!=TYPE_BYTE_ARRAY:
475 raise TypeError("xplra.MultiBuffer.getString: only byte arrays can be converted to strings")
476 if self._values is None:
477 self.finalize()
478 s = ""
479 for c in self._values[id]:
480 if c==0: break
481 s += chr(c)
482 return s
483
484 def _add(self, name, type, length = None, offset = None):
485 """Add a scalar to the buffer with the given name and type"""
486 index = len(self._dataRefs)
487 self._values = None
488 self._dataRefs.append( (name, type, length, offset) )
489 return index
490
491 def _writeSpec(self, command):
492 """Write the specification preceded by the given command and check for
493 the result.
494
495 The specification is basically the list of the datarefs."""
496 self._xplane._writeU8(command)
497 self._xplane._writeU32(len(self._dataRefs))
498
499 for (name, type, length, offset) in self._dataRefs:
500 self._xplane._writeString(name)
501 self._xplane._writeU8(type)
502 if length is not None and offset is not None:
503 self._xplane._writeS32(length)
504 self._xplane._writeS32(offset)
505
506 self._xplane._flush()
507 self._xplane._checkResult()
508
509 def __len__(self):
510 """Get the number of value items in the buffer."""
511 if self._values is None:
512 self.finalize()
513 return len(self._values)
514
515 def __getitem__(self, id):
516 """Get the item with the given ID."""
517 if self._values is None:
518 self.finalize()
519 return self._values[id]
520
521 def __setitem__(self, id, value):
522 """Set the item with the given ID."""
523 if self._values is None:
524 self.finalize()
525 type = self._dataRefs[id][1]
526 if type==TYPE_INT:
527 self._values[id] = int(value)
528 elif type==TYPE_FLOAT:
529 self._values[id] = float(value)
530 elif type==TYPE_DOUBLE:
531 self._values[id] = double(value)
532 elif type==TYPE_FLOAT_ARRAY:
533 self._values[id] = [float(x) for x in value]
534 elif type==TYPE_INT_ARRAY:
535 self._values[id] = [int(x) for x in value]
536 elif type==TYPE_BYTE_ARRAY:
537 if instanceof(value, str):
538 self._values[id] = [ord(x) for x in value]
539 else:
540 self._values[id] = [int(x) for x in value]
541
542 def __iter__(self):
543 """Get an iterator over the values of this buffer."""
544 if self._values is None:
545 self.finalize()
546 return iter(self._values)
547
548
549#-------------------------------------------------------------------------------
550
551class MultiGetter(MultiBuffer):
552 """Multi-dataref buffer for querying."""
553 def __init__(self, xplane):
554 """Construct the getter."""
555 super(MultiGetter, self).__init__(xplane,
556 COMMAND_REGISTER_GET_MULTI,
557 COMMAND_UNREGISTER_GET_MULTI)
558
559 def _executeRegistered(self):
560 """Execute the query if the buffer is registered."""
561 self._xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
562 self._xplane._writeU32(self._registeredID)
563 self._xplane._flush()
564
565 self._xplane._checkResult()
566
567 self._readValues()
568
569 def _executeUnregistered(self):
570 """Execute the query if the buffer is not registered."""
571 self._writeSpec(COMMAND_GET_MULTI)
572 self._readValues()
573
574 def _readValues(self):
575 """Read the values into the values array."""
576 for i in range(0, len(self._dataRefs)):
577 self._values[i] = self._xplane._readValue(self._dataRefs[i][1])
578
579#-------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.