source: xplra/src/client/python/xplra.py@ 67:1f08d2ceff1a

Last change on this file since 67:1f08d2ceff1a was 66:f7c8521991df, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the Doxygen configuration file and updated the documentation to eliminate the warnings

File size: 32.3 KB
Line 
1# X-Plane Remote Access client module
2#-------------------------------------------------------------------------------
3
4import os
5import struct
6import array
7
8#-------------------------------------------------------------------------------
9
10## @package xplra
11#
12# Python client module and C/C++ client library for the X-Plane Remote
13# Access plugin
14
15#-------------------------------------------------------------------------------
16
17## Protocol command: query the value of a single dataref
18COMMAND_GET_SINGLE = 0x01
19
20## Protocol command: set the value of a single dataref
21COMMAND_SET_SINGLE = 0x02
22
23## Protocol command: query the value of several datarefs
24COMMAND_GET_MULTI = 0x03
25
26## Protocol command: set the value of several datarefs
27COMMAND_SET_MULTI = 0x04
28
29## Protocol command: register a multi-dataref getter
30COMMAND_REGISTER_GET_MULTI = 0x11
31
32## Protocol command: unregister a multi-dataref getter
33COMMAND_UNREGISTER_GET_MULTI = 0x12
34
35## Protocol command: execute a registered multi-dataref getter
36COMMAND_EXECUTE_GET_MULTI = 0x13
37
38## Protocol command: register a multi-dataref setter
39COMMAND_REGISTER_SET_MULTI = 0x21
40
41## Protocol command: unregister a multi-dataref setter
42COMMAND_UNREGISTER_SET_MULTI = 0x22
43
44## Protocol command: execute a registered multi-dataref setter
45COMMAND_EXECUTE_SET_MULTI = 0x23
46
47## Protocol command: get the versions of X-Plane, XPLM and XPLRA
48COMMAND_GET_VERSIONS = 0x31
49
50## Protocol command: reload all plugins
51COMMAND_RELOAD_PLUGINS = 0x32
52
53## Protocol command: show a message to the pilot
54COMMAND_SHOW_MESSAGE = 0x41
55
56## Protocol command: register hotkeys
57COMMAND_REGISTER_HOTKEYS = 0x51
58
59## Protocol command: query the status of registered hotkeys
60COMMAND_QUERY_HOTKEYS = 0x52
61
62## Protocol command: unregister hotkeys
63COMMAND_UNREGISTER_HOTKEYS = 0x53
64
65## Protocol type constant: integer
66TYPE_INT = 0x01
67
68## Protocol type constant: single-precision floating point
69TYPE_FLOAT = 0x02
70
71## Protocol type constant: double-precision floating point
72TYPE_DOUBLE = 0x03
73
74## Protocol type constant: array of single-precision floating point values
75TYPE_FLOAT_ARRAY = 0x11
76
77## Protocol type constant: array of integers
78TYPE_INT_ARRAY = 0x12
79
80## Protocol type constant: array of bytes
81TYPE_BYTE_ARRAY = 0x13
82
83## Protocol result: OK
84RESULT_OK = 0x00
85
86## Protocol result: an invalid command was sent
87RESULT_INVALID_COMMAND = 0x01
88
89## Protocol result: an unknown dataref was attempted to query or set
90RESULT_UNKNOWN_DATAREF = 0x02
91
92## Protocol result: invalid type
93RESULT_INVALID_TYPE = 0x03
94
95## Protocol result: invalid length
96RESULT_INVALID_LENGTH = 0x04
97
98## Protocol result: invalid offset
99RESULT_INVALID_OFFSET = 0x05
100
101## Protocol result: invalid count
102RESULT_INVALID_COUNT = 0x06
103
104## Protocol result: invalid ID
105RESULT_INVALID_ID = 0x07
106
107## Protocol result: invalid duration
108RESULT_INVALID_DURATION = 0x08
109
110## Protocol result: other error
111RESULT_OTHER_ERROR = 0xff
112
113## Hotkey modifier: Shift
114HOTKEY_MODIFIER_SHIFT = 0x0100
115
116## Hotkey modifier: Control
117HOTKEY_MODIFIER_CONTROL = 0x0200
118
119#-------------------------------------------------------------------------------
120
121class ProtocolException(Exception):
122 """Exception to signify protocol errors."""
123
124 ## mapping from result codes to their string representation
125 _message = { RESULT_INVALID_COMMAND : "invalid command",
126 RESULT_UNKNOWN_DATAREF : "unknown dataref",
127 RESULT_INVALID_TYPE : "invalid type",
128 RESULT_INVALID_LENGTH : "invalid length",
129 RESULT_INVALID_OFFSET : "invalid offset",
130 RESULT_INVALID_COUNT : "invalid count",
131 RESULT_INVALID_ID : "invalid ID",
132 RESULT_INVALID_DURATION : "invalid duration",
133 RESULT_OTHER_ERROR : "other error" }
134
135 ## @var resultCode
136 # the result code, one of the RESULT_XXX constants
137 ## @var parameter
138 # an optional parameter for the result
139
140 @staticmethod
141 def getMessage(resultCode):
142 """Get the message for the given result code."""
143 if resultCode in ProtocolException._message:
144 return ProtocolException._message[resultCode]
145 else:
146 return "unknown error code " + `resultCode`
147
148 def __init__(self, resultCode, parameter = None):
149 """Construct the exception."""
150 message = "xplra.ProtocolException: " + self.getMessage(resultCode)
151 if parameter is not None:
152 if resultCode==RESULT_UNKNOWN_DATAREF:
153 message += " (# %d)" % (parameter,)
154
155 super(ProtocolException, self).__init__(message)
156 self.resultCode = resultCode
157 self.parameter = parameter
158
159#-------------------------------------------------------------------------------
160
161if os.name=="nt":
162 import win32file
163 import io
164
165 class Win32NamedPipe(io.RawIOBase):
166 """A stream object to represent a Win32 named pipe."""
167 ## @var _handle
168 # the Windows file handle for the pipe
169
170 def __init__(self, name):
171 """Construct the pipe with the given name."""
172 self._handle = win32file.CreateFile(name,
173 win32file.GENERIC_READ |
174 win32file.GENERIC_WRITE,
175 0, None,
176 win32file.OPEN_EXISTING, 0, None)
177
178 def close(self):
179 """Close the pipe, if not closed already."""
180 if self._handle is not None:
181 win32file.CloseHandle(self._handle)
182 self._handle = None
183
184 @property
185 def closed(self):
186 """Determine if the pipe is closed or not."""
187 return self._handle is None
188
189 def fileno(self):
190 """Get the file number, which is impossible."""
191 raise IOError("Win32NamedPipe.fileno: not supported")
192
193 def flush(self):
194 """Flush the stream."""
195 pass
196
197 def isatty(self):
198 """Determine if the stream is a terminal, which it is not."""
199 return False
200
201 def read(self, n = -1):
202 """Read at most the given number of bytes from the stream."""
203 if n==-1:
204 return self.readall()
205 else:
206 (error, data) = win32file.ReadFile(self._handle, n)
207 return data
208
209 def readable(self):
210 """Determine if the stream is readable, which it is."""
211 return self._handle is not None
212
213 def readall(self):
214 """Read all bytes from the stream, which is not supported."""
215 raise IOError("Win32NamedPipe.readall: not supported")
216
217 def readinto(self, buffer):
218 """Read into the given buffer."""
219 (error, data) = win32file.ReadFile(self._handle, len(buffer))
220 length = len(data)
221 buffer[:length] = data
222 return length
223
224 def readline(self, limit = -1):
225 """Read a line, which is currently not supported."""
226 raise IOError("Win32NamedPipe.readline: not supported")
227
228 def readlines(self, hint = -1):
229 """Read lines, which is currently not supported."""
230 raise IOError("Win32NamedPipe.readlines: not supported")
231
232 def seek(self, offset, whence = io.SEEK_SET):
233 """Seek in the stream, which is not supported."""
234 raise IOError("Win32NamedPipe.seek: not supported")
235
236 def seekable(self):
237 """Determine if the stream is seekable, which it is not."""
238 return False
239
240 def tell(self):
241 """Get the current position, which is not supported."""
242 raise IOError("Win32NamedPipe.tell: not supported")
243
244 def truncate(self, size = None):
245 """Truncate the stream, which is not supported."""
246 raise IOError("Win32NamedPipe.truncate: not supported")
247
248 def writable(self):
249 """Determine if the stream is writable, which it is."""
250 return self._handle is not None
251
252 def write(self, buffer):
253 """Write the given buffer into the stream."""
254 (error, written) = win32file.WriteFile(self._handle, buffer.tobytes())
255 return written
256
257 def writelines(self, lines):
258 """Write the given lines, which is not supported."""
259 raise IOError("Win32NamedPipe.writelines: not supported")
260
261#-------------------------------------------------------------------------------
262
263class XPlane(object):
264 """The main class representing the connection to X-Plane."""
265
266 ## @var _stream
267 # the data stream used to communicate with the plugin
268 ## @var _multiBuffers
269 # the list of multi-dataref buffers belonging to this object
270
271 @staticmethod
272 def _packLength(x):
273 """Pack the given integer as a variable-length value."""
274 s = ""
275 while True:
276 y = x&0x7f
277 x >>= 7
278 if x>0: y |= 0x80
279 s += struct.pack("B", y)
280 if x==0:
281 return s
282
283 @staticmethod
284 def _packString(s):
285 """Pack the given string."""
286 return XPlane._packLength(len(s)) + s
287
288 def __init__(self):
289 """Construct the object."""
290 self._stream = None
291 self._multiBuffers = []
292
293 def connect(self):
294 """Try to connect to X-Plane."""
295 if self._stream is not None:
296 return
297
298 if os.name=="nt":
299 pipe = Win32NamedPipe(r'\\.\pipe\\xplra')
300 self._stream = io.BufferedRWPair(pipe, pipe)
301 else:
302 import socket
303 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
304 s.connect("/tmp/xplra-" + os.environ["LOGNAME"])
305 self._stream = s.makefile()
306
307 for multiBuffer in self._multiBuffers:
308 multiBuffer._reregister()
309
310 @property
311 def isConnected(self):
312 """Determine if we are connected to the simulator."""
313 return self._stream is not None
314
315 def createMultiGetter(self):
316 """Create a new multi-dataref getter for this X-Plane object."""
317 multiGetter = MultiGetter(self)
318 self._multiBuffers.append(multiGetter)
319 return multiGetter
320
321 def createMultiSetter(self):
322 """Create a new multi-dataref setter for this X-Plane object."""
323 multiSetter = MultiSetter(self)
324 self._multiBuffers.append(multiSetter)
325 return multiSetter
326
327 def destroyMultiBuffer(self, multiBuffer):
328 """Destroy the given multi-dataref buffer.
329
330 It actually removes it from the list of multiple buffers and
331 unregisters it safely.
332
333 Returns the result of the safe unregistration if the buffer was found,
334 None otherwise.
335 """
336 if multiBuffer in self._multiBuffers:
337 self._multiBuffers.remove(multiBuffer)
338 return multiBuffer.unregisterSafely()
339 else:
340 return None
341
342 def getVersions(self):
343 """Get the versions of X-Plane, XPLM and XPLRA as a tuple."""
344 self._writeU8(COMMAND_GET_VERSIONS)
345 self._flush()
346 self._checkResult()
347 return (self._readS32(), self._readS32(), self._readS32())
348
349 def reloadPlugins(self):
350 """Reload the plugins in X-Plane.
351
352 After this, this connection becomes invalid."""
353 self._writeU8(COMMAND_RELOAD_PLUGINS)
354 self._flush()
355 self._checkResult();
356
357 def getInt(self, name):
358 """Get the value of the integer dataref with the given name."""
359 return self._getSingle(name, TYPE_INT)
360
361 def getFloat(self, name):
362 """Get the value of the float dataref with the given name."""
363 return self._getSingle(name, TYPE_FLOAT)
364
365 def getDouble(self, name):
366 """Get the value of the double dataref with the given name."""
367 return self._getSingle(name, TYPE_DOUBLE)
368
369 def getFloatArray(self, name, length = -1, offset = 0):
370 """Get the value of the float array dataref with the given name."""
371 return self._getSingle(name, TYPE_FLOAT_ARRAY, length, offset)
372
373 def getIntArray(self, name, length = -1, offset = 0):
374 """Get the value of the integer array dataref with the given name."""
375 return self._getSingle(name, TYPE_INT_ARRAY, length, offset)
376
377 def getByteArray(self, name, length = -1, offset = 0):
378 """Get the value of the byte array dataref with the given name."""
379 return self._getSingle(name, TYPE_BYTE_ARRAY, length, offset)
380
381 def getString(self, name, offset = 0):
382 """Get the value of the byte array dataref with the given name as a
383 string."""
384 s = ""
385 for b in self.getByteArray(name, offset = offset):
386 if b==0: break
387 s += chr(b)
388 return s
389
390 def setInt(self, name, value):
391 """Set the value of the integer dataref with the given name."""
392 self._setSingle(name, TYPE_INT, value)
393
394 def setFloat(self, name, value):
395 """Set the value of the float dataref with the given name."""
396 self._setSingle(name, TYPE_FLOAT, value)
397
398 def setDouble(self, name, value):
399 """Set the value of the double dataref with the given name."""
400 self._setSingle(name, TYPE_DOUBLE, value)
401
402 def setFloatArray(self, name, value, offset = 0):
403 """Set the value of the float array dataref with the given name."""
404 self._setSingle(name, TYPE_FLOAT_ARRAY, value, offset = offset)
405
406 def setIntArray(self, name, value, offset = 0):
407 """Set the value of the integer array dataref with the given name."""
408 self._setSingle(name, TYPE_INT_ARRAY, value, offset = offset)
409
410 def setByteArray(self, name, value, offset = 0):
411 """Set the value of the byte array dataref with the given name."""
412 self._setSingle(name, TYPE_BYTE_ARRAY, value, offset = offset)
413
414 def setString(self, name, value, length, offset = 0):
415 """Set the value of the byte array dataref with the given name from a
416 string.
417
418 The string will be padded with 0's so that its length is the given
419 one."""
420 value = [ord(c) for c in value[:length]]
421 value += [0] * (length - len(value))
422 self.setByteArray(name, value, offset)
423
424 def showMessage(self, message, duration):
425 """Show a message in the simulator window for the given duration.
426
427 The duration is a floating-point number denoting seconds."""
428 self._writeU8(COMMAND_SHOW_MESSAGE)
429 self._writeString(message)
430 self._writeFloat(duration)
431 self._flush()
432 self._checkResult()
433
434 def registerHotkeys(self, hotkeyCodes):
435 """Register the given hotkey codes for watching."""
436 self._writeU8(COMMAND_REGISTER_HOTKEYS)
437 self._writeU32(len(hotkeyCodes))
438 for hotkeyCode in hotkeyCodes:
439 self._writeU16(hotkeyCode)
440 self._flush()
441 self._checkResult()
442
443 def queryHotkeys(self):
444 """Query the state of the hotkeys registered previously"""
445 self._writeU8(COMMAND_QUERY_HOTKEYS)
446 self._flush()
447 self._checkResult()
448
449 length = self._readU32()
450 states = []
451 for i in range(0, length):
452 states.append(self._readU8()!=0)
453
454 return states
455
456 def unregisterHotkeys(self):
457 """Unregister the previously registered hotkeys."""
458 self._writeU8(COMMAND_UNREGISTER_HOTKEYS)
459 self._flush()
460 self._checkResult()
461
462 def disconnect(self):
463 """Disconnect from X-Plane."""
464 if self._stream is not None:
465 try:
466 self._stream.close()
467 finally:
468 self._stream = None
469
470 def _checkResult(self, resultCode = None, parameter = None, multi = False):
471 """Check the given result code.
472
473 If it is None, it will be read first.
474
475 If it is not RESULT_OK, a ProtocolException is thrown."""
476
477 if resultCode is None:
478 resultCode = self._readU8()
479 if multi and resultCode==RESULT_UNKNOWN_DATAREF:
480 parameter = self._readU32()
481
482 if resultCode!=RESULT_OK:
483 raise ProtocolException(resultCode, parameter)
484
485 def _getSingle(self, name, type, length = None, offset = None):
486 """Get the single value of the given name and type."""
487 self._writeU8(COMMAND_GET_SINGLE)
488 self._writeString(name)
489 self._writeU8(type)
490 if length is not None and offset is not None:
491 self._writeS32(length)
492 self._writeS32(offset)
493
494 self._flush()
495 self._checkResult()
496 return self._readValue(type)
497
498 def _readValue(self, type):
499 """Read the value of the given type from the stream."""
500 if type==TYPE_INT:
501 return self._readS32()
502 elif type==TYPE_FLOAT:
503 return self._readFloat()
504 elif type==TYPE_DOUBLE:
505 return self._readDouble()
506 else:
507 length = self._readS32()
508 arr = None
509 elementSize = 4
510 if type==TYPE_FLOAT_ARRAY:
511 arr = array.array("f")
512 elif type==TYPE_INT_ARRAY:
513 arr = array.array("i")
514 elif type==TYPE_BYTE_ARRAY:
515 arr = array.array("B")
516 elementSize = 1
517 if arr is not None and length>0:
518 arr.fromstring(self._stream.read(length*elementSize))
519 return None if arr is None else arr.tolist()
520
521 def _setSingle(self, name, type, value, offset = None):
522 """Set the single value of the given name and type."""
523 self._writeU8(COMMAND_SET_SINGLE)
524 self._writeString(name)
525 self._writeU8(type)
526 if offset is not None:
527 self._writeS32(len(value))
528 self._writeS32(offset)
529
530 self._writeValue(type, value)
531 self._flush()
532 self._checkResult()
533
534 def _writeValue(self, type, value):
535 """Write a value of the given type."""
536 if type==TYPE_INT:
537 self._writeS32(int(value))
538 elif type==TYPE_FLOAT:
539 self._writeFloat(float(value))
540 elif type==TYPE_DOUBLE:
541 self._writeDouble(float(value))
542 else:
543 arr = None
544 if type==TYPE_FLOAT_ARRAY:
545 arr = array.array("f")
546 elif type==TYPE_INT_ARRAY:
547 arr = array.array("i")
548 elif type==TYPE_BYTE_ARRAY:
549 arr = array.array("B")
550 arr.fromlist(value)
551 self._stream.write(arr.tostring())
552
553 def _writeU8(self, x):
554 """Write the given value as an unsigned, 8-bit value."""
555 self._stream.write(struct.pack("B", x))
556
557 def _writeU16(self, x):
558 """Write the given value as an unsigned, 16-bit value."""
559 self._stream.write(struct.pack("H", x))
560
561 def _writeS32(self, x):
562 """Write the given value as a signed, 32-bit value."""
563 self._stream.write(struct.pack("i", x))
564
565 def _writeU32(self, x):
566 """Write the given value as an unsigned, 32-bit value."""
567 self._stream.write(struct.pack("I", x))
568
569 def _writeFloat(self, x):
570 """Write the given value as a single-precision floating point."""
571 self._stream.write(struct.pack("f", x))
572
573 def _writeDouble(self, x):
574 """Write the given value as a double-precision floating point."""
575 self._stream.write(struct.pack("d", x))
576
577 def _writeLength(self, length):
578 """Write the given value is a variable-length value into our stream."""
579 self._stream.write(self._packLength(length))
580
581 def _writeString(self, str):
582 """Write the given string into the stream."""
583 self._stream.write(self._packString(str))
584
585 def _flush(self):
586 """Flush our stream."""
587 self._stream.flush()
588
589 def _readU8(self):
590 """Read an unsigned, 8-bit value from the stream."""
591 (value,) = struct.unpack("B", self._stream.read(1))
592 return value
593
594 def _readS32(self):
595 """Read a signed, 32-bit value from the stream."""
596 (value,) = struct.unpack("i", self._stream.read(4))
597 return value
598
599 def _readU32(self):
600 """Read an unsigned, 32-bit value from the stream."""
601 (value,) = struct.unpack("I", self._stream.read(4))
602 return value
603
604 def _readFloat(self):
605 """Read a single-precision floating point value from the stream."""
606 (value,) = struct.unpack("f", self._stream.read(4))
607 return value
608
609 def _readDouble(self):
610 """Read a double-precision floating point value from the stream."""
611 (value,) = struct.unpack("d", self._stream.read(8))
612 return value
613
614 def _readLength(self):
615 """Read a variable-length value from our stream."""
616 length = 0
617 while True:
618 (x,) = struct.unpack("B", self._stream.read(1))
619 length <<= 7
620 length |= x&0x7f
621 if x&0x80==0:
622 return length
623
624 def _readString(self):
625 """Read a string from our stream."""
626 length = self._readLength()
627 return self._stream.read(length)
628
629#-------------------------------------------------------------------------------
630
631class MultiBuffer(object):
632 """Buffer for querying or setting multi-dataref values."""
633
634 ## @var _xplane
635 # the \ref XPlane object this buffer belongs to
636
637 ## @var _registerCommand
638 # the command used to perform the registration of this buffer
639 # (\ref COMMAND_REGISTER_GET_MULTI or \ref COMMAND_REGISTER_SET_MULTI)
640
641 ## @var _unregisterCommand
642 # the command used to perform the registration of this buffer
643 # (\ref COMMAND_UNREGISTER_GET_MULTI or \ref COMMAND_UNREGISTER_SET_MULTI)
644
645 ## @var _dataRefs
646 # the datarefs belonging to the buffer
647
648 ## @var _values
649 # the value of the datarefs before setting or after querying
650
651 ## @var _registeredID
652 # the ID with which the buffer is registered in X-Plane
653
654 @staticmethod
655 def _getDefault(type, length):
656 """Get the default value for the given type."""
657 if type==TYPE_INT:
658 return 0
659 elif type==TYPE_FLOAT:
660 return float(0.0)
661 elif type==TYPE_DOUBLE:
662 return 0.0
663 elif length<=0:
664 return []
665 elif type==TYPE_FLOAT_ARRAY:
666 return [float(0.0)] * length
667 elif type==TYPE_INT_ARRAY or type==TYPE_BYTE_ARRAY:
668 return [0] * length
669
670 def __init__(self, xplane, registerCommand, unregisterCommand):
671 """Construct the buffer for the given XPlane instance and with the
672 given register/unregister command values."""
673 self._xplane = xplane
674 self._registerCommand = registerCommand
675 self._unregisterCommand = unregisterCommand
676
677 self._dataRefs = []
678 self._values = None
679
680 self._registeredID = None
681
682 @property
683 def values(self):
684 """Query the values as a list."""
685 if self._values is None:
686 self.finalize()
687 return self._values
688
689 def addInt(self, name):
690 """Add an integer to the buffer with the given name
691
692 Returns an ID (or index) of the dataref."""
693 return self._add(name, TYPE_INT)
694
695 def addFloat(self, name):
696 """Add a float to the buffer with the given name
697
698 Returns an ID (or index) of the dataref."""
699 return self._add(name, TYPE_FLOAT)
700
701 def addDouble(self, name):
702 """Add a double to the buffer with the given name
703
704 Returns an ID (or index) of the dataref."""
705 return self._add(name, TYPE_DOUBLE)
706
707 def addFloatArray(self, name, length = -1, offset = 0):
708 """Add a floating point array to the buffer with the given name.
709
710 Returns an ID (or index) of the dataref."""
711 return self._add(name, TYPE_FLOAT_ARRAY, length, offset)
712
713 def addIntArray(self, name, length = -1, offset = 0):
714 """Add an integer array to the buffer with the given name.
715
716 Returns an ID (or index) of the dataref."""
717 return self._add(name, TYPE_INT_ARRAY, length, offset)
718
719 def addByteArray(self, name, length = -1, offset = 0):
720 """Add a byte array to the buffer with the given name.
721
722 Returns an ID (or index) of the dataref."""
723 return self._add(name, TYPE_BYTE_ARRAY, length, offset)
724
725 def finalize(self):
726 """Finalize the buffer, if not finalized yet.
727
728 It initializes the array of values with some defaults.
729
730 Returns whether there is any data in the buffer."""
731 if self._values is None:
732 self._values = [self._getDefault(type, length)
733 for (_, type, length, _) in self._dataRefs]
734 return self._values!=[]
735
736 def register(self):
737 """Register the buffer in X-Plane."""
738 if self.finalize() and self._registeredID is None:
739 self._writeSpec(self._registerCommand)
740 self._registeredID = self._xplane._readU32()
741
742 def unregister(self):
743 """Unregister the buffer from X-Plane."""
744 if self._registeredID is not None:
745 self._xplane._writeU8(self._unregisterCommand)
746 self._xplane._writeU32(self._registeredID)
747 self._xplane._flush()
748 self._xplane._checkResult()
749 self._registeredID = None
750
751 def unregisterSafely(self):
752 """Unregister the buffer from X-Plane, ignoring exceptions.
753
754 Returns True if the unregistration succeeded, False otherwise.
755 """
756 try:
757 self.unregister()
758 return True
759 except:
760 return False
761 finally:
762 self._registeredID = None
763
764 def execute(self):
765 """Perform the querying or the setting of the values.
766
767 It first checks if the buffer is finalized. If not, it will be
768 finalized. However, if it is not finalized but also registered, it will
769 first be unregistered and the re-registered after finalizing."""
770 if self._values is None and self._registeredID is not None:
771 self.unregister()
772 self.register()
773 else:
774 self.finalize()
775
776 if self._registeredID is None:
777 self._executeUnregistered()
778 else:
779 self._executeRegistered()
780
781 def getString(self, id):
782 """Get the value of the dataref with the given ID as a string.
783
784 The dataref should be of type byte array."""
785 if self._dataRefs[id][1]!=TYPE_BYTE_ARRAY:
786 raise TypeError("xplra.MultiBuffer.getString: only byte arrays can be converted to strings")
787 if self._values is None:
788 self.finalize()
789 s = ""
790 for c in self._values[id]:
791 if c==0: break
792 s += chr(c)
793 return s
794
795 def _reregister(self):
796 """Re-register the buffer in X-Plane, if it has been registered earlier
797
798 If it has not been registered, nothing is done. Otherwise the buffer
799 gets registered, and the old ID is forgotten. This function is meant to
800 be used by the XPlane object when it creates a new connection. If the
801 registration fails, the original ID is restored, so the _reregister()
802 could be called again
803 """
804 if self._registeredID is not None:
805 origRegisteredID = self._registeredID
806 try:
807 self._registeredID = None
808 self.register()
809 except:
810 self._registeredID = origRegisteredID
811 raise
812
813 def _add(self, name, type, length = None, offset = None):
814 """Add a scalar to the buffer with the given name and type"""
815 index = len(self._dataRefs)
816 self._values = None
817 self._dataRefs.append( (name, type, length, offset) )
818 return index
819
820 def _writeSpec(self, command):
821 """Write the specification preceded by the given command and check for
822 the result.
823
824 The specification is basically the list of the datarefs."""
825 self._xplane._writeU8(command)
826 self._xplane._writeU32(len(self._dataRefs))
827
828 for (name, type, length, offset) in self._dataRefs:
829 self._xplane._writeString(name)
830 self._xplane._writeU8(type)
831 if length is not None and offset is not None:
832 self._xplane._writeS32(length)
833 self._xplane._writeS32(offset)
834
835 self._xplane._flush()
836 self._xplane._checkResult(multi = True)
837
838 def __len__(self):
839 """Get the number of value items in the buffer."""
840 if self._values is None:
841 self.finalize()
842 return len(self._values)
843
844 def __getitem__(self, id):
845 """Get the item with the given ID."""
846 if self._values is None:
847 self.finalize()
848 return self._values[id]
849
850 def __setitem__(self, id, value):
851 """Set the item with the given ID."""
852 if self._values is None:
853 self.finalize()
854 type = self._dataRefs[id][1]
855 length = self._dataRefs[id][2]
856 if type==TYPE_INT:
857 self._values[id] = int(value)
858 elif type==TYPE_FLOAT or type==TYPE_DOUBLE:
859 self._values[id] = float(value)
860 elif type==TYPE_FLOAT_ARRAY:
861 if len(value)!=length:
862 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
863 self._values[id] = [float(x) for x in value]
864 elif type==TYPE_INT_ARRAY:
865 if len(value)!=length:
866 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
867 self._values[id] = [int(x) for x in value]
868 elif type==TYPE_BYTE_ARRAY:
869 if isinstance(value, str):
870 lst = [ord(x) for x in value[:length]]
871 if len(lst)<length:
872 lst += [0] * (length-len(lst))
873 self._values[id] = lst
874 else:
875 if len(value)!=length:
876 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
877 self._values[id] = [int(x) for x in value]
878
879 def __iter__(self):
880 """Get an iterator over the values of this buffer."""
881 if self._values is None:
882 self.finalize()
883 return iter(self._values)
884
885
886#-------------------------------------------------------------------------------
887
888class MultiGetter(MultiBuffer):
889 """Multi-dataref buffer for querying."""
890 def __init__(self, xplane):
891 """Construct the getter."""
892 super(MultiGetter, self).__init__(xplane,
893 COMMAND_REGISTER_GET_MULTI,
894 COMMAND_UNREGISTER_GET_MULTI)
895
896 def _executeRegistered(self):
897 """Execute the query if the buffer is registered."""
898 self._xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
899 self._xplane._writeU32(self._registeredID)
900 self._xplane._flush()
901
902 self._xplane._checkResult(multi = True)
903
904 self._readValues()
905
906 def _executeUnregistered(self):
907 """Execute the query if the buffer is not registered."""
908 self._writeSpec(COMMAND_GET_MULTI)
909 self._readValues()
910
911 def _readValues(self):
912 """Read the values into the values array."""
913 for i in range(0, len(self._dataRefs)):
914 self._values[i] = self._xplane._readValue(self._dataRefs[i][1])
915
916#-------------------------------------------------------------------------------
917
918class MultiSetter(MultiBuffer):
919 """Multi-dataref buffer for setting."""
920 def __init__(self, xplane):
921 """Construct the getter."""
922 super(MultiSetter, self).__init__(xplane,
923 COMMAND_REGISTER_SET_MULTI,
924 COMMAND_UNREGISTER_SET_MULTI)
925
926 def _executeRegistered(self):
927 """Execute the query if the buffer is registered."""
928 self._xplane._writeU8(COMMAND_EXECUTE_SET_MULTI)
929 self._xplane._writeU32(self._registeredID)
930 for i in range(0, len(self._dataRefs)):
931 self._xplane._writeValue(self._dataRefs[i][1], self._values[i])
932 self._xplane._flush()
933
934 self._xplane._checkResult(multi = True)
935
936 def _executeUnregistered(self):
937 """Execute the query if the buffer is not registered."""
938 self._xplane._writeU8(COMMAND_SET_MULTI)
939
940 numDataRefs = len(self._dataRefs)
941 self._xplane._writeU32(numDataRefs)
942
943 for i in range(0, numDataRefs):
944 (name, type, length, offset) = self._dataRefs[i]
945 value = self._values[i]
946
947 self._xplane._writeString(name)
948 self._xplane._writeU8(type)
949 if length is not None and offset is not None:
950 self._xplane._writeS32(len(value))
951 self._xplane._writeS32(offset)
952 self._xplane._writeValue(type, value)
953
954 self._xplane._flush()
955 self._xplane._checkResult(multi = True)
956
957#-------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.