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