source: xplra/src/client/python/xplra.py@ 70:7882bccb87a0

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

Added the description of the C API

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