source: xplra/src/client/python/xplra.py@ 70:7882bccb87a0

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

Added the description of the C API

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