source: xplra/src/client/python/xplra.py@ 103:c55961369c07

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

Using frombytes() an array instead of fromstring()

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