source: xplra/src/client/python/xplra.py@ 83:f31da5d1718d

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

Added a client call to save the current situation

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