source: xplra/misc/client.py

Last change on this file was 97:28052c8e62c9, checked in by István Váradi <ivaradi@…>, 5 years ago

Some helper scripts are ported to Python 3

  • Property exe set to *
File size: 18.5 KB
Line 
1#!/usr/bin/env python3
2
3import sys
4import cmd
5import struct
6import socket
7import os
8
9#------------------------------------------------------------------------------
10
11class CLI(cmd.Cmd):
12 """Simple command-line interface for the X-Plane Remote Access plugin."""
13 _types = { "i" : 0x01,
14 "f" : 0x02,
15 "d" : 0x03,
16 "fa" : 0x11,
17 "ia" : 0x12,
18 "ba" : 0x13,
19 "s" : 0x13 }
20
21 @staticmethod
22 def _splitArgs(args):
23 """Split the given argument string into a list of words."""
24 words = []
25 word = ""
26 inQuote = False
27 for c in args:
28 if c.isspace() and not inQuote:
29 if word:
30 words.append(word)
31 word = ""
32 elif c=="\"" and (not word or inQuote):
33 inQuote = not inQuote
34 else:
35 word += c
36 if word:
37 words.append(word)
38 return words
39
40 @staticmethod
41 def _parseGetSpec(words):
42 """Parse a data query specification from the given argument word lisst.
43
44 If there is some failure, it returns None.
45
46 Otherwise it returns a tuple of two items:
47 - the parsed data, which is a tuple of these items:
48 - the name of the dataref to query
49 - the type of the dataref to query (it is a valid type, checked by
50 the function)
51 - the length of the dataref to query
52 - the offset to query the dataref from
53
54 - the remainder of the argument list."""
55 if len(words)<2:
56 print("Missing parameters", file=sys.stderr)
57 return None
58
59 nextIndex = 2
60
61 name = words[0]
62 type = words[1]
63 if type not in CLI._types:
64 print("Invalid type", file=sys.stderr)
65 return None
66
67 length = None
68 offset = None
69 if type in ["ia", "fa", "ba", "s"]:
70 if len(words)<4:
71 print("Missing parameters", file=sys.stderr)
72 return None
73 length = int(words[2])
74 offset = int(words[3])
75 nextIndex = 4
76
77 return ((name, type, length, offset), words[nextIndex:])
78
79 @staticmethod
80 def _packLength(x):
81 """Pack the given integer as a variable-length value."""
82 s = bytes()
83 while True:
84 y = x&0x7f
85 x >>= 7
86 if x>0: y |= 0x80
87 s += struct.pack("B", y)
88 if x==0:
89 return s
90
91 @staticmethod
92 def _packString(s):
93 """Pack the given string."""
94 return CLI._packLength(len(s)) + bytes(s, "utf-8")
95
96 def __init__(self, stream):
97 """Construct the CLI."""
98 cmd.Cmd.__init__(self)
99
100 self._stream = stream
101 self._multiGets = {}
102 self._multiSets = {}
103
104 self.use_rawinput = True
105 self.intro = "\nX-Plane Remote Access plugin command prompt\n"
106 self.prompt = "XPLRA> "
107
108 self.daemon = True
109
110 def default(self, line):
111 """Handle uhandled commands"""
112 if line=="EOF":
113 print()
114 return True
115 else:
116 return super(CLI, self).default(line)
117
118 def do_get(self, args):
119 """Handle the 'get' command"""
120 words = self._splitArgs(args)
121 result = self._parseGetSpec(words)
122 if result is None:
123 return False
124
125 (name, type, length, offset) = result[0]
126
127 self._writeU8(0x01)
128 self._writeString(name)
129 self._writeU8(self._types[type])
130 if length is not None:
131 self._writeS32(length)
132 self._writeS32(offset)
133 self._flush()
134
135 result = self._readU8()
136 if result==0:
137 print(self._readValue(type))
138 else:
139 print("Error code:", result, file=sys.stderr)
140
141 def do_set(self, args):
142 """Handle the 'set' command"""
143 words = self._splitArgs(args)
144 if len(words)<3:
145 print("Missing parameters", file=sys.stderr)
146 return False
147
148 name = words[0]
149
150 type = words[1]
151 if type not in self._types:
152 print("Invalid type", file=sys.stderr)
153 return False
154
155 value = words[2]
156
157 length = None
158 offset = None
159 if len(words)>4:
160 length = int(words[3])
161 offset = int(words[4])
162
163 self._writeU8(0x02)
164 self._writeString(name)
165 self._writeU8(self._types[type])
166 if length is not None:
167 self._writeS32(length)
168 self._writeS32(offset)
169 self._writeValue(type, value, length)
170
171 self._flush()
172
173 result = self._readU8()
174 if result!=0:
175 print("Error code:", result, file=sys.stderr)
176
177 def do_multiget(self, args):
178 """Handle the 'multiget' command"""
179 words = self._splitArgs(args)
180 specs = []
181 while words:
182 result = self._parseGetSpec(words)
183 if result is None:
184 return False
185 else:
186 specs.append(result[0])
187 words = result[1]
188
189 if not specs:
190 return False
191
192 self._writeU8(0x03)
193 self._writeU32(len(specs))
194 for (name, type, length, offset) in specs:
195 self._writeString(name)
196 self._writeU8(self._types[type])
197 if length is not None:
198 self._writeS32(length)
199 self._writeS32(offset)
200 self._flush()
201
202 result = self._readU8()
203 if result==0:
204 for (_, type, _, _) in specs:
205 value = self._readValue(type)
206 print(value)
207 elif result==0x02:
208 index = self._readU32()
209 print("Invalid dataref at #%d" % (index,), file=sys.stderr)
210 else:
211 print("Error code:", result, file=sys.stderr)
212
213 def do_multiset(self, args):
214 """Handle the 'multiset' command"""
215 words = self._splitArgs(args)
216 specs = []
217 values = []
218 while words:
219 result = self._parseGetSpec(words)
220 if result is None:
221 return False
222 else:
223 words = result[1]
224 if not words:
225 print("Missing argument", file=sys.stderr)
226 return False
227 specs.append(result[0])
228 values.append(words[0])
229 words = words[1:]
230
231 if not specs:
232 return False
233
234 self._writeU8(0x04)
235 self._writeU32(len(specs))
236 for ((name, type, length, offset), value) in zip(specs, values):
237 self._writeString(name)
238 self._writeU8(self._types[type])
239 if length is not None:
240 self._writeS32(length)
241 self._writeS32(offset)
242 self._writeValue(type, value, length)
243 self._flush()
244
245 result = self._readU8()
246 if result==0x02:
247 index = self._readU32()
248 print("Invalid dataref at #%d" % (index,), file=sys.stderr)
249 elif result!=0:
250 print("Error code:", result, file=sys.stderr)
251
252 def do_reg_multiget(self, args):
253 """Handle the 'reg_multiget' command"""
254 words = self._splitArgs(args)
255 specs = []
256 while words:
257 result = self._parseGetSpec(words)
258 if result is None:
259 return False
260 else:
261 specs.append(result[0])
262 words = result[1]
263
264 if not specs:
265 return False
266
267 self._writeU8(0x11)
268 self._writeU32(len(specs))
269 for (name, type, length, offset) in specs:
270 self._writeString(name)
271 self._writeU8(self._types[type])
272 if length is not None:
273 self._writeS32(length)
274 self._writeS32(offset)
275 self._flush()
276
277 result = self._readU8()
278 if result==0:
279 id = self._readU32()
280 self._multiGets[id] = \
281 [type for (name, type, length, offset) in specs]
282 print("ID:", id)
283 else:
284 print("Error code:", result, file=sys.stderr)
285
286 def do_unreg_multiget(self, args):
287 """Handle the 'unreg_multiget' command"""
288 words = self._splitArgs(args)
289 if len(words)<1:
290 print("Missing parameter", file=sys.stderr)
291 return False
292
293 id = int(words[0])
294
295 self._writeU8(0x12)
296 self._writeU32(id)
297 self._flush()
298
299 result = self._readU8()
300 if result!=0:
301 print("Error code:", result, file=sys.stderr)
302
303 if id in self._multiGets:
304 del self._multiGets[id]
305
306 def do_exec_multiget(self, args):
307 """Handle the 'exec_multiget' command"""
308 words = self._splitArgs(args)
309 if len(words)<1:
310 print("Missing parameter", file=sys.stderr)
311 return False
312
313 id = int(words[0])
314 if id not in self._multiGets:
315 print("Invalid ID", file=sys.stderr)
316 return False
317
318 self._writeU8(0x13)
319 self._writeU32(id)
320 self._flush()
321
322 result = self._readU8()
323 if result==0:
324 for type in self._multiGets[id]:
325 value = self._readValue(type)
326 print(value)
327 elif result==0x02:
328 index = self._readU32()
329 print("Invalid dataref at #%d" % (index,), file=sys.stderr)
330 else:
331 print("Error code:", result, file=sys.stderr)
332
333 def do_reg_multiset(self, args):
334 """Handle the 'reg_multiset' command"""
335 words = self._splitArgs(args)
336 specs = []
337 while words:
338 result = self._parseGetSpec(words)
339 if result is None:
340 return False
341 else:
342 specs.append(result[0])
343 words = result[1]
344
345 if not specs:
346 return False
347
348 self._writeU8(0x21)
349 self._writeU32(len(specs))
350 for (name, type, length, offset) in specs:
351 self._writeString(name)
352 self._writeU8(self._types[type])
353 if length is not None:
354 self._writeS32(length)
355 self._writeS32(offset)
356 self._flush()
357
358 result = self._readU8()
359 if result==0:
360 id = self._readU32()
361 self._multiSets[id] = \
362 [(type, length) for (name, type, length, offset) in specs]
363 print("ID:", id)
364 else:
365 print("Error code:", result, file=sys.stderr)
366
367 def do_unreg_multiset(self, args):
368 """Handle the 'unreg_multiset' command"""
369 words = self._splitArgs(args)
370 if len(words)<1:
371 print("Missing parameter", file=sys.stderr)
372 return False
373
374 id = int(words[0])
375
376 self._writeU8(0x22)
377 self._writeU32(id)
378 self._flush()
379
380 result = self._readU8()
381 if result!=0:
382 print("Error code:", result, file=sys.stderr)
383
384 if id in self._multiSets:
385 del self._multiSets[id]
386
387 def do_exec_multiset(self, args):
388 """Handle the 'exec_multiget' command"""
389 words = self._splitArgs(args)
390 if len(words)<1:
391 print("Missing parameter", file=sys.stderr)
392 return False
393
394 id = int(words[0])
395 if id not in self._multiSets:
396 print("Invalid ID", file=sys.stderr)
397 return False
398
399 words = words[1:]
400 types = self._multiSets[id]
401 if len(words)<len(types):
402 print("Missing parameter", file=sys.stderr)
403 return False
404
405
406 self._writeU8(0x23)
407 self._writeU32(id)
408 for ((type, length), word) in zip(types, words):
409 self._writeValue(type, word, length)
410 self._flush()
411
412 result = self._readU8()
413 if result==0x02:
414 index = self._readU32()
415 print("Invalid dataref at #%d" % (index,), file=sys.stderr)
416 elif result!=0:
417 print("Error code:", result, file=sys.stderr)
418
419 def do_show_message(self, args):
420 words = self._splitArgs(args)
421 if len(words)<2:
422 print("Missing parameters", file=sys.stderr)
423 return False
424
425 message = words[0]
426 duration = float(words[1])
427
428 self._writeU8(0x41)
429 self._writeString(message)
430 self._writeFloat(duration)
431 self._flush()
432
433 result = self._readU8()
434 if result!=0:
435 print("Error code:", result, file=sys.stderr)
436
437 def do_reload(self, args):
438 self._writeU8(0x32)
439 self._flush()
440
441 result = self._readU8()
442 if result!=0:
443 print("Error code:", result, file=sys.stderr)
444
445 def do_register_hotkeys(self, args):
446 words = self._splitArgs(args)
447 if not words:
448 print("Missing parameters", file=sys.stderr)
449 return False
450
451 self._writeU8(0x51)
452 self._writeU32(len(words))
453 for word in words:
454 self._writeU16(int(word, 16))
455 self._flush()
456
457 result = self._readU8()
458 if result!=0:
459 print("Error code:", result, file=sys.stderr)
460
461 def do_query_hotkeys(self, args):
462 self._writeU8(0x52)
463 self._flush()
464
465 result = self._readU8()
466 if result==0:
467 length = self._readU32()
468 values = []
469 for i in range(0, length):
470 values.append(self._readU8())
471 print(values)
472 else:
473 print("Error code:", result, file=sys.stderr)
474
475 def do_unregister_hotkeys(self, args):
476 self._writeU8(0x53)
477 self._flush()
478
479 result = self._readU8()
480 if result!=0:
481 print("Error code:", result, file=sys.stderr)
482
483 def _writeU8(self, x):
484 """Write the given value as an unsigned, 8-bit value."""
485 self._stream.write(struct.pack("B", x))
486
487 def _writeU16(self, x):
488 """Write the given value as an unsigned, 16-bit value."""
489 self._stream.write(struct.pack("H", x))
490
491 def _writeS32(self, x):
492 """Write the given value as a signed, 32-bit value."""
493 self._stream.write(struct.pack("i", x))
494
495 def _writeU32(self, x):
496 """Write the given value as an unsigned, 32-bit value."""
497 self._stream.write(struct.pack("I", x))
498
499 def _writeFloat(self, x):
500 """Write the given value as a single-precision floating point."""
501 self._stream.write(struct.pack("f", x))
502
503 def _writeDouble(self, x):
504 """Write the given value as a double-precision floating point."""
505 self._stream.write(struct.pack("d", x))
506
507 def _writeLength(self, length):
508 """Write the given value is a variable-length value into our stream."""
509 self._stream.write(self._packLength(length))
510
511 def _writeString(self, str):
512 """Write the given string into the stream."""
513 self._stream.write(self._packString(str))
514
515 def _flush(self):
516 """Flush our stream."""
517 self._stream.flush()
518
519 def _readU8(self):
520 """Read an unsigned, 8-bit value from the stream."""
521 (value,) = struct.unpack("B", self._stream.read(1))
522 return value
523
524 def _readS32(self):
525 """Read a signed, 32-bit value from the stream."""
526 (value,) = struct.unpack("i", self._stream.read(4))
527 return value
528
529 def _readU32(self):
530 """Read an unsigned, 32-bit value from the stream."""
531 (value,) = struct.unpack("I", self._stream.read(4))
532 return value
533
534 def _readFloat(self):
535 """Read a single-precision floating point value from the stream."""
536 (value,) = struct.unpack("f", self._stream.read(4))
537 return value
538
539 def _readDouble(self):
540 """Read a double-precision floating point value from the stream."""
541 (value,) = struct.unpack("d", self._stream.read(8))
542 return value
543
544 def _readLength(self):
545 """Read a variable-length value from our stream."""
546 length = 0
547 while True:
548 (x,) = struct.unpack("B", self._stream.read(1))
549 length <<= 7
550 length |= x&0x7f
551 if x&0x80==0:
552 return length
553
554 def _readString(self):
555 """Read a string from our stream."""
556 length = self._readLength()
557 return self._stream.read(length)
558
559 def _readValue(self, type):
560 """Read the value of the given type from the stream."""
561 if type=="i":
562 return self._readS32()
563 elif type=="f":
564 return self._readFloat()
565 elif type=="d":
566 return self._readDouble()
567 elif type in ["fa", "ia", "ba", "s"]:
568 length = self._readS32()
569 if length>8192:
570 print("Very big length:", length)
571 return None
572 elif length>0:
573 if type=="fa":
574 return [self._readFloat() for i in range(0, length)]
575 elif type=="ia":
576 return [self._readS32() for i in range(0, length)]
577 elif type=="ba" or type=="s":
578 bytes = [self._readU8() for i in range(0, length)]
579 if type=="ba":
580 return bytes
581 else:
582 string = ""
583 for b in bytes:
584 if b==0: break
585 string += chr(b)
586 return string
587 elif length==0:
588 return []
589 else:
590 return None
591
592 def _writeValue(self, type, value, length = None):
593 """Write the given value of the given type to the stream."""
594 if type=="i":
595 self._writeS32(int(value))
596 elif type=="f":
597 self._writeFloat(float(value))
598 elif type=="d":
599 self._writeDouble(float(value))
600 elif type in ["fa", "ia", "ba", "s"]:
601 if type=="s":
602 values = [ord(c) for c in value]
603 else:
604 words = [word.strip() for word in value.split(",")]
605 values = [float(word) if type=="fa" else int(word)
606 for word in words]
607 if len(values)<length:
608 values += [0.0 if type=="fa" else 0] * (length-len(values))
609 elif len(values)>length:
610 values = values[:length]
611 for value in values:
612 if type=="fa":
613 self._writeFloat(value)
614 elif type=="ia":
615 self._writeS32(value)
616 else:
617 self._writeU8(value)
618
619#------------------------------------------------------------------------------
620
621if __name__ == "__main__":
622 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
623 s.connect("/tmp/xplra-" + os.environ["LOGNAME"])
624 CLI(s.makefile("rwb")).cmdloop()
Note: See TracBrowser for help on using the repository browser.