9 isPy3 = sys.version_info[0] >= 3
20 COMMAND_GET_SINGLE = 0x01
23 COMMAND_SET_SINGLE = 0x02
26 COMMAND_GET_MULTI = 0x03
29 COMMAND_SET_MULTI = 0x04
32 COMMAND_REGISTER_GET_MULTI = 0x11
35 COMMAND_UNREGISTER_GET_MULTI = 0x12
38 COMMAND_EXECUTE_GET_MULTI = 0x13
41 COMMAND_REGISTER_SET_MULTI = 0x21
44 COMMAND_UNREGISTER_SET_MULTI = 0x22
47 COMMAND_EXECUTE_SET_MULTI = 0x23
50 COMMAND_GET_VERSIONS = 0x31
53 COMMAND_RELOAD_PLUGINS = 0x32
56 COMMAND_SAVE_SITUATION = 0x33
59 COMMAND_SHOW_MESSAGE = 0x41
62 COMMAND_REGISTER_HOTKEYS = 0x51
65 COMMAND_QUERY_HOTKEYS = 0x52
68 COMMAND_UNREGISTER_HOTKEYS = 0x53
80 TYPE_FLOAT_ARRAY = 0x11
86 TYPE_BYTE_ARRAY = 0x13
92 RESULT_INVALID_COMMAND = 0x01
95 RESULT_UNKNOWN_DATAREF = 0x02
98 RESULT_INVALID_TYPE = 0x03
101 RESULT_INVALID_LENGTH = 0x04
104 RESULT_INVALID_OFFSET = 0x05
107 RESULT_INVALID_COUNT = 0x06
110 RESULT_INVALID_ID = 0x07
113 RESULT_INVALID_DURATION = 0x08
116 RESULT_OTHER_ERROR = 0xff
119 HOTKEY_MODIFIER_SHIFT = 0x0100
122 HOTKEY_MODIFIER_CONTROL = 0x0200
127 DEFAULT_TCP_PORT = 0x5852;
132 """Exception to signify protocol errors."""
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" }
152 """Get the message for the given result code."""
153 if resultCode
in ProtocolException._message:
154 return ProtocolException._message[resultCode]
156 return "unknown error code " + repr(resultCode)
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,)
165 super(ProtocolException, self).
__init__(message)
176 """A stream object to represent a Win32 named pipe."""
181 """Construct the pipe with the given name."""
182 self.
_handle_handle = win32file.CreateFile(name,
183 win32file.GENERIC_READ |
184 win32file.GENERIC_WRITE,
186 win32file.OPEN_EXISTING, 0,
None)
189 """Close the pipe, if not closed already."""
190 if self.
_handle_handle
is not None:
191 win32file.CloseHandle(self.
_handle_handle)
196 """Determine if the pipe is closed or not."""
197 return self.
_handle_handle
is None
200 """Get the file number, which is impossible."""
201 raise IOError(
"Win32NamedPipe.fileno: not supported")
204 """Flush the stream."""
208 """Determine if the stream is a terminal, which it is not."""
212 """Read at most the given number of bytes from the stream."""
216 (error, data) = win32file.ReadFile(self.
_handle_handle, n)
220 """Determine if the stream is readable, which it is."""
221 return self.
_handle_handle
is not None
224 """Read all bytes from the stream, which is not supported."""
225 raise IOError(
"Win32NamedPipe.readall: not supported")
228 """Read into the given buffer."""
229 (error, data) = win32file.ReadFile(self.
_handle_handle, len(buffer))
231 buffer[:length] = data
235 """Read a line, which is currently not supported."""
236 raise IOError(
"Win32NamedPipe.readline: not supported")
239 """Read lines, which is currently not supported."""
240 raise IOError(
"Win32NamedPipe.readlines: not supported")
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")
247 """Determine if the stream is seekable, which it is not."""
251 """Get the current position, which is not supported."""
252 raise IOError(
"Win32NamedPipe.tell: not supported")
255 """Truncate the stream, which is not supported."""
256 raise IOError(
"Win32NamedPipe.truncate: not supported")
259 """Determine if the stream is writable, which it is."""
260 return self.
_handle_handle
is not None
263 """Write the given buffer into the stream."""
264 (error, written) = win32file.WriteFile(self.
_handle_handle, buffer.tobytes())
268 """Write the given lines, which is not supported."""
269 raise IOError(
"Win32NamedPipe.writelines: not supported")
274 """The main class representing the connection to X-Plane."""
283 """Pack the given integer as a variable-length value."""
284 s = bytes()
if isPy3
else ""
289 s += struct.pack(
"B", y)
295 """Pack the given string."""
296 return XPlane._packLength(len(s)) + (bytes(s,
"utf-8")
if isPy3
else s)
299 """Construct the object."""
304 """Try to connect to X-Plane."""
305 if self.
_stream_stream
is not None:
309 if address.startswith(
"tcp:"):
310 address = address[4:]
311 colonIndex = address.find(
":")
313 port = DEFAULT_TCP_PORT
315 port = int(address[colonIndex+1:])
316 address = address[:colonIndex]
319 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
320 print(
"Connecting to TCP address '%s':%d" %
322 s.connect((address, port))
323 self.
_stream_stream = s.makefile(
"rwb")
324 elif address!=
"local":
325 raise ValueError(
"Invalid address: " + address)
327 if self.
_stream_stream
is None:
330 self.
_stream_stream = io.BufferedRWPair(pipe, pipe)
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")
338 multiBuffer._reregister()
342 """Determine if we are connected to the simulator."""
343 return self.
_stream_stream
is not None
346 """Create a new multi-dataref getter for this X-Plane object."""
352 """Create a new multi-dataref setter for this X-Plane object."""
358 """Destroy the given multi-dataref buffer.
360 It actually removes it from the list of multiple buffers and
361 unregisters it safely.
363 Returns the result of the safe unregistration if the buffer was found,
368 return multiBuffer.unregisterSafely()
373 """Get the versions of X-Plane, XPLM and XPLRA as a tuple."""
374 self.
_writeU8_writeU8(COMMAND_GET_VERSIONS)
380 """Reload the plugins in X-Plane.
382 After this, this connection becomes invalid."""
383 self.
_writeU8_writeU8(COMMAND_RELOAD_PLUGINS)
388 """Save the current situation into the given path."""
389 self.
_writeU8_writeU8(COMMAND_SAVE_SITUATION)
395 """Get the value of the integer dataref with the given name."""
396 return self.
_getSingle_getSingle(name, TYPE_INT)
399 """Get the value of the float dataref with the given name."""
400 return self.
_getSingle_getSingle(name, TYPE_FLOAT)
403 """Get the value of the double dataref with the given name."""
404 return self.
_getSingle_getSingle(name, TYPE_DOUBLE)
407 """Get the value of the float array dataref with the given name."""
408 return self.
_getSingle_getSingle(name, TYPE_FLOAT_ARRAY, length, offset)
411 """Get the value of the integer array dataref with the given name."""
412 return self.
_getSingle_getSingle(name, TYPE_INT_ARRAY, length, offset)
415 """Get the value of the byte array dataref with the given name."""
416 return self.
_getSingle_getSingle(name, TYPE_BYTE_ARRAY, length, offset)
419 """Get the value of the byte array dataref with the given name as a
422 for b
in self.
getByteArraygetByteArray(name, offset = offset):
428 """Set the value of the integer dataref with the given name."""
429 self.
_setSingle_setSingle(name, TYPE_INT, value)
432 """Set the value of the float dataref with the given name."""
433 self.
_setSingle_setSingle(name, TYPE_FLOAT, value)
436 """Set the value of the double dataref with the given name."""
437 self.
_setSingle_setSingle(name, TYPE_DOUBLE, value)
440 """Set the value of the float array dataref with the given name."""
441 self.
_setSingle_setSingle(name, TYPE_FLOAT_ARRAY, value, offset = offset)
444 """Set the value of the integer array dataref with the given name."""
445 self.
_setSingle_setSingle(name, TYPE_INT_ARRAY, value, offset = offset)
448 """Set the value of the byte array dataref with the given name."""
449 self.
_setSingle_setSingle(name, TYPE_BYTE_ARRAY, value, offset = offset)
452 """Set the value of the byte array dataref with the given name from a
455 The string will be padded with 0's so that its length is the given
457 value = [ord(c)
for c
in value[:length]]
458 value += [0] * (length - len(value))
462 """Show a message in the simulator window for the given duration.
464 The duration is a floating-point number denoting seconds."""
465 self.
_writeU8_writeU8(COMMAND_SHOW_MESSAGE)
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:
481 """Query the state of the hotkeys registered previously"""
482 self.
_writeU8_writeU8(COMMAND_QUERY_HOTKEYS)
488 for i
in range(0, length):
489 states.append(self.
_readU8_readU8()!=0)
494 """Unregister the previously registered hotkeys."""
495 self.
_writeU8_writeU8(COMMAND_UNREGISTER_HOTKEYS)
500 """Disconnect from X-Plane."""
501 if self.
_stream_stream
is not None:
507 def _checkResult(self, resultCode = None, parameter = None, multi = False):
508 """Check the given result code.
510 If it is None, it will be read first.
512 If it is not RESULT_OK, a ProtocolException is thrown."""
514 if resultCode
is None:
515 resultCode = self.
_readU8_readU8()
516 if multi
and resultCode==RESULT_UNKNOWN_DATAREF:
519 if resultCode!=RESULT_OK:
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)
527 if length
is not None and offset
is not None:
536 """Read the value of the given type from the stream."""
539 elif type==TYPE_FLOAT:
541 elif type==TYPE_DOUBLE:
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")
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()
559 """Set the single value of the given name and type."""
560 self.
_writeU8_writeU8(COMMAND_SET_SINGLE)
563 if offset
is not None:
572 """Write a value of the given type."""
575 elif type==TYPE_FLOAT:
577 elif type==TYPE_DOUBLE:
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")
588 self.
_stream_stream.write(arr.tostring())
591 """Write the given value as an unsigned, 8-bit value."""
592 self.
_stream_stream.write(struct.pack(
"B", x))
595 """Write the given value as an unsigned, 16-bit value."""
596 self.
_stream_stream.write(struct.pack(
"H", x))
599 """Write the given value as a signed, 32-bit value."""
600 self.
_stream_stream.write(struct.pack(
"i", x))
603 """Write the given value as an unsigned, 32-bit value."""
604 self.
_stream_stream.write(struct.pack(
"I", x))
607 """Write the given value as a single-precision floating point."""
608 self.
_stream_stream.write(struct.pack(
"f", x))
611 """Write the given value as a double-precision floating point."""
612 self.
_stream_stream.write(struct.pack(
"d", x))
615 """Write the given value is a variable-length value into our stream."""
619 """Write the given string into the stream."""
623 """Flush our stream."""
627 """Read an unsigned, 8-bit value from the stream."""
628 (value,) = struct.unpack(
"B", self.
_stream_stream.read(1))
632 """Read a signed, 32-bit value from the stream."""
633 (value,) = struct.unpack(
"i", self.
_stream_stream.read(4))
637 """Read an unsigned, 32-bit value from the stream."""
638 (value,) = struct.unpack(
"I", self.
_stream_stream.read(4))
642 """Read a single-precision floating point value from the stream."""
643 (value,) = struct.unpack(
"f", self.
_stream_stream.read(4))
647 """Read a double-precision floating point value from the stream."""
648 (value,) = struct.unpack(
"d", self.
_stream_stream.read(8))
652 """Read a variable-length value from our stream."""
655 (x,) = struct.unpack(
"B", self.
_stream_stream.read(1))
662 """Read a string from our stream."""
664 return self.
_stream_stream.read(length)
669 """Buffer for querying or setting multi-dataref values."""
693 """Get the default value for the given type."""
696 elif type==TYPE_FLOAT:
698 elif type==TYPE_DOUBLE:
702 elif type==TYPE_FLOAT_ARRAY:
703 return [float(0.0)] * length
704 elif type==TYPE_INT_ARRAY
or type==TYPE_BYTE_ARRAY:
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."""
721 """Query the values as a list."""
722 if self.
_values_values
is None:
727 """Add an integer to the buffer with the given name
729 Returns an ID (or index) of the dataref."""
730 return self.
_add_add(name, TYPE_INT)
733 """Add a float to the buffer with the given name
735 Returns an ID (or index) of the dataref."""
736 return self.
_add_add(name, TYPE_FLOAT)
739 """Add a double to the buffer with the given name
741 Returns an ID (or index) of the dataref."""
742 return self.
_add_add(name, TYPE_DOUBLE)
745 """Add a floating point array to the buffer with the given name.
747 Returns an ID (or index) of the dataref."""
748 return self.
_add_add(name, TYPE_FLOAT_ARRAY, length, offset)
751 """Add an integer array to the buffer with the given name.
753 Returns an ID (or index) of the dataref."""
754 return self.
_add_add(name, TYPE_INT_ARRAY, length, offset)
757 """Add a byte array to the buffer with the given name.
759 Returns an ID (or index) of the dataref."""
760 return self.
_add_add(name, TYPE_BYTE_ARRAY, length, offset)
763 """Finalize the buffer, if not finalized yet.
765 It initializes the array of values with some defaults.
767 Returns whether there is any data in the buffer."""
768 if self.
_values_values
is None:
770 for (_, type, length, _)
in self.
_dataRefs_dataRefs]
774 """Register the buffer in X-Plane."""
780 """Unregister the buffer from X-Plane."""
785 self.
_xplane_xplane._checkResult()
789 """Unregister the buffer from X-Plane, ignoring exceptions.
791 Returns True if the unregistration succeeded, False otherwise.
802 """Perform the querying or the setting of the values.
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."""
814 self._executeUnregistered()
816 self._executeRegistered()
819 """Get the value of the dataref with the given ID as a string.
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:
827 for c
in self.
_values_values[id]:
833 """Re-register the buffer in X-Plane, if it has been registered earlier
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
850 def _add(self, name, type, length = None, offset = None):
851 """Add a scalar to the buffer with the given name and type"""
854 self.
_dataRefs_dataRefs.append( (name, type, length, offset) )
858 """Write the specification preceded by the given command and check for
861 The specification is basically the list of the datarefs."""
862 self.
_xplane_xplane._writeU8(command)
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)
873 self.
_xplane_xplane._checkResult(multi =
True)
876 """Get the number of value items in the buffer."""
877 if self.
_values_values
is None:
879 return len(self.
_values_values)
882 """Get the item with the given ID."""
883 if self.
_values_values
is None:
888 """Set the item with the given ID."""
889 if self.
_values_values
is None:
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]]
909 lst += [0] * (length-len(lst))
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]
917 """Get an iterator over the values of this buffer."""
918 if self.
_values_values
is None:
920 return iter(self.
_values_values)
926 """Multi-dataref buffer for querying."""
928 """Construct the getter."""
929 super(MultiGetter, self).
__init__(xplane,
930 COMMAND_REGISTER_GET_MULTI,
931 COMMAND_UNREGISTER_GET_MULTI)
934 """Execute the query if the buffer is registered."""
935 self.
_xplane_xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
939 self.
_xplane_xplane._checkResult(multi =
True)
944 """Execute the query if the buffer is not registered."""
949 """Read the values into the values array."""
950 for i
in range(0, len(self.
_dataRefs_dataRefs)):
956 """Multi-dataref buffer for setting."""
958 """Construct the getter."""
959 super(MultiSetter, self).
__init__(xplane,
960 COMMAND_REGISTER_SET_MULTI,
961 COMMAND_UNREGISTER_SET_MULTI)
964 """Execute the query if the buffer is registered."""
965 self.
_xplane_xplane._writeU8(COMMAND_EXECUTE_SET_MULTI)
967 for i
in range(0, len(self.
_dataRefs_dataRefs)):
971 self.
_xplane_xplane._checkResult(multi =
True)
974 """Execute the query if the buffer is not registered."""
975 self.
_xplane_xplane._writeU8(COMMAND_SET_MULTI)
977 numDataRefs = len(self.
_dataRefs_dataRefs)
978 self.
_xplane_xplane._writeU32(numDataRefs)
980 for i
in range(0, numDataRefs):
981 (name, type, length, offset) = self.
_dataRefs_dataRefs[i]
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)
992 self.
_xplane_xplane._checkResult(multi =
True)
def __getitem__(self, id)
def addDouble(self, name)
def unregisterSafely(self)
def _getDefault(type, length)
_dataRefs
the datarefs belonging to the buffer
def _writeSpec(self, command)
def addByteArray(self, name, length=-1, offset=0)
def _add(self, name, type, length=None, offset=None)
def __init__(self, xplane, registerCommand, unregisterCommand)
_values
the value of the datarefs before setting or after querying
def __setitem__(self, id, value)
_xplane
the XPlane object this buffer belongs to
_registeredID
the ID with which the buffer is registered in X-Plane
def addFloatArray(self, name, length=-1, offset=0)
_unregisterCommand
the command used to perform the registration of this buffer (COMMAND_UNREGISTER_GET_MULTI or COMMAND_...
def addIntArray(self, name, length=-1, offset=0)
_registerCommand
the command used to perform the registration of this buffer (COMMAND_REGISTER_GET_MULTI or COMMAND_RE...
def _executeRegistered(self)
def _executeUnregistered(self)
def __init__(self, xplane)
def _executeRegistered(self)
def _executeUnregistered(self)
def __init__(self, xplane)
resultCode
the result code, one of the RESULT_XXX constants
def getMessage(resultCode)
def __init__(self, resultCode, parameter=None)
parameter
an optional parameter for the result
_handle
the Windows file handle for the pipe
def readline(self, limit=-1)
def readinto(self, buffer)
def writelines(self, lines)
def truncate(self, size=None)
def readlines(self, hint=-1)
def seek(self, offset, whence=io.SEEK_SET)
def unregisterHotkeys(self)
def _writeValue(self, type, value)
def setFloat(self, name, value)
def getIntArray(self, name, length=-1, offset=0)
_stream
the data stream used to communicate with the plugin
def getDouble(self, name)
def destroyMultiBuffer(self, multiBuffer)
def _writeString(self, str)
def getByteArray(self, name, length=-1, offset=0)
def registerHotkeys(self, hotkeyCodes)
def getFloatArray(self, name, length=-1, offset=0)
def _setSingle(self, name, type, value, offset=None)
def connect(self, address=None)
def showMessage(self, message, duration)
def setByteArray(self, name, value, offset=0)
def getString(self, name, offset=0)
_multiBuffers
the list of multi-dataref buffers belonging to this object
def _writeDouble(self, x)
def setIntArray(self, name, value, offset=0)
def setFloatArray(self, name, value, offset=0)
def createMultiSetter(self)
def _writeLength(self, length)
def createMultiGetter(self)
def _readValue(self, type)
def saveSituation(self, path)
def setInt(self, name, value)
def setString(self, name, value, length, offset=0)
def _checkResult(self, resultCode=None, parameter=None, multi=False)
def _getSingle(self, name, type, length=None, offset=None)
def setDouble(self, name, value)