source: xplra/src/client/python/xplra.py@ 108:7b985f3a0ee1

Last change on this file since 108:7b985f3a0ee1 was 108:7b985f3a0ee1, checked in by István Váradi <ivaradi@…>, 17 months ago

The Python client can connect to X-Plane over TCP

File size: 33.6 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
126## The default TCP port ("XR")
127DEFAULT_TCP_PORT = 0x5852;
128
129#-------------------------------------------------------------------------------
130
131class ProtocolException(Exception):
132 """Exception to signify protocol errors."""
133
134 ## mapping from result codes to their string representation
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 ## @var resultCode
146 # the result code, one of the RESULT_XXX constants
147 ## @var parameter
148 # an optional parameter for the result
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.getMessage(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.resultCode = resultCode
167 self.parameter = parameter
168
169#-------------------------------------------------------------------------------
170
171if 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 ## @var _handle
178 # the Windows file handle for the pipe
179
180 def __init__(self, name):
181 """Construct the pipe with the given name."""
182 self._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 is not None:
191 win32file.CloseHandle(self._handle)
192 self._handle = None
193
194 @property
195 def closed(self):
196 """Determine if the pipe is closed or not."""
197 return self._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.readall()
215 else:
216 (error, data) = win32file.ReadFile(self._handle, n)
217 return data
218
219 def readable(self):
220 """Determine if the stream is readable, which it is."""
221 return self._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, 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 is not None
261
262 def write(self, buffer):
263 """Write the given buffer into the stream."""
264 (error, written) = win32file.WriteFile(self._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
273class XPlane(object):
274 """The main class representing the connection to X-Plane."""
275
276 ## @var _stream
277 # the data stream used to communicate with the plugin
278 ## @var _multiBuffers
279 # the list of multi-dataref buffers belonging to this object
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 = None
301 self._multiBuffers = []
302
303 def connect(self, address = None):
304 """Try to connect to X-Plane."""
305 if self._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 = s.makefile("rwb")
324 elif address!="local":
325 raise ValueError("Invalid address: " + address)
326
327 if self._stream is None:
328 if os.name=="nt":
329 pipe = Win32NamedPipe(r'\\.\pipe\\xplra')
330 self._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 = s.makefile("rwb")
336
337 for multiBuffer in self._multiBuffers:
338 multiBuffer._reregister()
339
340 @property
341 def isConnected(self):
342 """Determine if we are connected to the simulator."""
343 return self._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.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.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:
367 self._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(COMMAND_GET_VERSIONS)
375 self._flush()
376 self._checkResult()
377 return (self._readS32(), self._readS32(), self._readS32())
378
379 def reloadPlugins(self):
380 """Reload the plugins in X-Plane.
381
382 After this, this connection becomes invalid."""
383 self._writeU8(COMMAND_RELOAD_PLUGINS)
384 self._flush()
385 self._checkResult();
386
387 def saveSituation(self, path):
388 """Save the current situation into the given path."""
389 self._writeU8(COMMAND_SAVE_SITUATION)
390 self._writeString(path)
391 self._flush()
392 self._checkResult();
393
394 def getInt(self, name):
395 """Get the value of the integer dataref with the given name."""
396 return self._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(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(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(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(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(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.getByteArray(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(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(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(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(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(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(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.setByteArray(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(COMMAND_SHOW_MESSAGE)
466 self._writeString(message)
467 self._writeFloat(duration)
468 self._flush()
469 self._checkResult()
470
471 def registerHotkeys(self, hotkeyCodes):
472 """Register the given hotkey codes for watching."""
473 self._writeU8(COMMAND_REGISTER_HOTKEYS)
474 self._writeU32(len(hotkeyCodes))
475 for hotkeyCode in hotkeyCodes:
476 self._writeU16(hotkeyCode)
477 self._flush()
478 self._checkResult()
479
480 def queryHotkeys(self):
481 """Query the state of the hotkeys registered previously"""
482 self._writeU8(COMMAND_QUERY_HOTKEYS)
483 self._flush()
484 self._checkResult()
485
486 length = self._readU32()
487 states = []
488 for i in range(0, length):
489 states.append(self._readU8()!=0)
490
491 return states
492
493 def unregisterHotkeys(self):
494 """Unregister the previously registered hotkeys."""
495 self._writeU8(COMMAND_UNREGISTER_HOTKEYS)
496 self._flush()
497 self._checkResult()
498
499 def disconnect(self):
500 """Disconnect from X-Plane."""
501 if self._stream is not None:
502 try:
503 self._stream.close()
504 finally:
505 self._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()
516 if multi and resultCode==RESULT_UNKNOWN_DATAREF:
517 parameter = self._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(COMMAND_GET_SINGLE)
525 self._writeString(name)
526 self._writeU8(type)
527 if length is not None and offset is not None:
528 self._writeS32(length)
529 self._writeS32(offset)
530
531 self._flush()
532 self._checkResult()
533 return self._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()
539 elif type==TYPE_FLOAT:
540 return self._readFloat()
541 elif type==TYPE_DOUBLE:
542 return self._readDouble()
543 else:
544 length = self._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.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(COMMAND_SET_SINGLE)
561 self._writeString(name)
562 self._writeU8(type)
563 if offset is not None:
564 self._writeS32(len(value))
565 self._writeS32(offset)
566
567 self._writeValue(type, value)
568 self._flush()
569 self._checkResult()
570
571 def _writeValue(self, type, value):
572 """Write a value of the given type."""
573 if type==TYPE_INT:
574 self._writeS32(int(value))
575 elif type==TYPE_FLOAT:
576 self._writeFloat(float(value))
577 elif type==TYPE_DOUBLE:
578 self._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.write(arr.tostring())
589
590 def _writeU8(self, x):
591 """Write the given value as an unsigned, 8-bit value."""
592 self._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.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.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.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.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.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.write(self._packLength(length))
617
618 def _writeString(self, str):
619 """Write the given string into the stream."""
620 self._stream.write(self._packString(str))
621
622 def _flush(self):
623 """Flush our stream."""
624 self._stream.flush()
625
626 def _readU8(self):
627 """Read an unsigned, 8-bit value from the stream."""
628 (value,) = struct.unpack("B", self._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.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.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.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.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.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()
664 return self._stream.read(length)
665
666#-------------------------------------------------------------------------------
667
668class MultiBuffer(object):
669 """Buffer for querying or setting multi-dataref values."""
670
671 ## @var _xplane
672 # the \ref XPlane object this buffer belongs to
673
674 ## @var _registerCommand
675 # the command used to perform the registration of this buffer
676 # (\ref COMMAND_REGISTER_GET_MULTI or \ref COMMAND_REGISTER_SET_MULTI)
677
678 ## @var _unregisterCommand
679 # the command used to perform the registration of this buffer
680 # (\ref COMMAND_UNREGISTER_GET_MULTI or \ref COMMAND_UNREGISTER_SET_MULTI)
681
682 ## @var _dataRefs
683 # the datarefs belonging to the buffer
684
685 ## @var _values
686 # the value of the datarefs before setting or after querying
687
688 ## @var _registeredID
689 # the ID with which the buffer is registered in X-Plane
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
711 self._registerCommand = registerCommand
712 self._unregisterCommand = unregisterCommand
713
714 self._dataRefs = []
715 self._values = None
716
717 self._registeredID = None
718
719 @property
720 def values(self):
721 """Query the values as a list."""
722 if self._values is None:
723 self.finalize()
724 return self._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(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(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(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(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(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(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 is None:
769 self._values = [self._getDefault(type, length)
770 for (_, type, length, _) in self._dataRefs]
771 return self._values!=[]
772
773 def register(self):
774 """Register the buffer in X-Plane."""
775 if self.finalize() and self._registeredID is None:
776 self._writeSpec(self._registerCommand)
777 self._registeredID = self._xplane._readU32()
778
779 def unregister(self):
780 """Unregister the buffer from X-Plane."""
781 if self._registeredID is not None:
782 self._xplane._writeU8(self._unregisterCommand)
783 self._xplane._writeU32(self._registeredID)
784 self._xplane._flush()
785 self._xplane._checkResult()
786 self._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.unregister()
795 return True
796 except:
797 return False
798 finally:
799 self._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 is None and self._registeredID is not None:
808 self.unregister()
809 self.register()
810 else:
811 self.finalize()
812
813 if self._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[id][1]!=TYPE_BYTE_ARRAY:
823 raise TypeError("xplra.MultiBuffer.getString: only byte arrays can be converted to strings")
824 if self._values is None:
825 self.finalize()
826 s = ""
827 for c in self._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 is not None:
842 origRegisteredID = self._registeredID
843 try:
844 self._registeredID = None
845 self.register()
846 except:
847 self._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)
853 self._values = None
854 self._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._writeU8(command)
863 self._xplane._writeU32(len(self._dataRefs))
864
865 for (name, type, length, offset) in self._dataRefs:
866 self._xplane._writeString(name)
867 self._xplane._writeU8(type)
868 if length is not None and offset is not None:
869 self._xplane._writeS32(length)
870 self._xplane._writeS32(offset)
871
872 self._xplane._flush()
873 self._xplane._checkResult(multi = True)
874
875 def __len__(self):
876 """Get the number of value items in the buffer."""
877 if self._values is None:
878 self.finalize()
879 return len(self._values)
880
881 def __getitem__(self, id):
882 """Get the item with the given ID."""
883 if self._values is None:
884 self.finalize()
885 return self._values[id]
886
887 def __setitem__(self, id, value):
888 """Set the item with the given ID."""
889 if self._values is None:
890 self.finalize()
891 type = self._dataRefs[id][1]
892 length = self._dataRefs[id][2]
893 if type==TYPE_INT:
894 self._values[id] = int(value)
895 elif type==TYPE_FLOAT or type==TYPE_DOUBLE:
896 self._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[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[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[id] = lst
911 else:
912 if len(value)!=length:
913 raise ValueError("xplra.MultiBuffer: expected a list of length %d" % (length,))
914 self._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 is None:
919 self.finalize()
920 return iter(self._values)
921
922
923#-------------------------------------------------------------------------------
924
925class MultiGetter(MultiBuffer):
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
933 def _executeRegistered(self):
934 """Execute the query if the buffer is registered."""
935 self._xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
936 self._xplane._writeU32(self._registeredID)
937 self._xplane._flush()
938
939 self._xplane._checkResult(multi = True)
940
941 self._readValues()
942
943 def _executeUnregistered(self):
944 """Execute the query if the buffer is not registered."""
945 self._writeSpec(COMMAND_GET_MULTI)
946 self._readValues()
947
948 def _readValues(self):
949 """Read the values into the values array."""
950 for i in range(0, len(self._dataRefs)):
951 self._values[i] = self._xplane._readValue(self._dataRefs[i][1])
952
953#-------------------------------------------------------------------------------
954
955class MultiSetter(MultiBuffer):
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
963 def _executeRegistered(self):
964 """Execute the query if the buffer is registered."""
965 self._xplane._writeU8(COMMAND_EXECUTE_SET_MULTI)
966 self._xplane._writeU32(self._registeredID)
967 for i in range(0, len(self._dataRefs)):
968 self._xplane._writeValue(self._dataRefs[i][1], self._values[i])
969 self._xplane._flush()
970
971 self._xplane._checkResult(multi = True)
972
973 def _executeUnregistered(self):
974 """Execute the query if the buffer is not registered."""
975 self._xplane._writeU8(COMMAND_SET_MULTI)
976
977 numDataRefs = len(self._dataRefs)
978 self._xplane._writeU32(numDataRefs)
979
980 for i in range(0, numDataRefs):
981 (name, type, length, offset) = self._dataRefs[i]
982 value = self._values[i]
983
984 self._xplane._writeString(name)
985 self._xplane._writeU8(type)
986 if length is not None and offset is not None:
987 self._xplane._writeS32(len(value))
988 self._xplane._writeS32(offset)
989 self._xplane._writeValue(type, value)
990
991 self._xplane._flush()
992 self._xplane._checkResult(multi = True)
993
994#-------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.