yostlabs 2025.1.10__py3-none-any.whl → 2025.2.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yostlabs/communication/base.py +3 -2
- yostlabs/tss3/api.py +710 -684
- yostlabs/tss3/consts.py +40 -1
- {yostlabs-2025.1.10.dist-info → yostlabs-2025.2.10.dist-info}/METADATA +3 -1
- {yostlabs-2025.1.10.dist-info → yostlabs-2025.2.10.dist-info}/RECORD +7 -7
- {yostlabs-2025.1.10.dist-info → yostlabs-2025.2.10.dist-info}/WHEEL +0 -0
- {yostlabs-2025.1.10.dist-info → yostlabs-2025.2.10.dist-info}/licenses/LICENSE +0 -0
yostlabs/tss3/api.py
CHANGED
|
@@ -5,10 +5,12 @@ from yostlabs.communication.serial import ThreespaceSerialComClass
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from typing import TypeVar, Generic
|
|
8
|
+
from collections.abc import Callable
|
|
8
9
|
import struct
|
|
9
10
|
import types
|
|
10
11
|
import inspect
|
|
11
12
|
import time
|
|
13
|
+
import math
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
#For converting from internal format specifiers to struct module specifiers
|
|
@@ -61,10 +63,11 @@ class ThreespaceCommand:
|
|
|
61
63
|
BINARY_START_BYTE = 0xf7
|
|
62
64
|
BINARY_START_BYTE_HEADER = 0xf9
|
|
63
65
|
|
|
64
|
-
def __init__(self, name: str, num: int, in_format: str, out_format: str):
|
|
66
|
+
def __init__(self, name: str, num: int, in_format: str, out_format: str, custom_func: Callable = None):
|
|
65
67
|
self.info = ThreespaceCommandInfo(name, num, in_format, out_format)
|
|
66
68
|
self.in_format = _3space_format_to_external(self.info.in_format)
|
|
67
69
|
self.out_format = _3space_format_to_external(self.info.out_format)
|
|
70
|
+
self.custom_func = custom_func
|
|
68
71
|
|
|
69
72
|
def format_cmd(self, *args, header_enabled=False):
|
|
70
73
|
cmd_data = struct.pack("<B", self.info.num)
|
|
@@ -86,23 +89,29 @@ class ThreespaceCommand:
|
|
|
86
89
|
def parse_response(self, response: bytes):
|
|
87
90
|
if self.info.num_out_params == 0: return None
|
|
88
91
|
output = []
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
|
|
93
|
+
if math.isnan(self.info.out_size): #Has strings in it, must slow parse
|
|
94
|
+
for c in self.out_format:
|
|
95
|
+
if c != 's':
|
|
96
|
+
format_str = f"<{c}"
|
|
97
|
+
size = struct.calcsize(format_str)
|
|
98
|
+
output.append(struct.unpack(format_str, response[:size])[0])
|
|
99
|
+
#TODO: Switch to using numpy views instead of slicing
|
|
100
|
+
response = response[size:]
|
|
101
|
+
else: #Strings are special, find the null terminator
|
|
102
|
+
str_len = response.index(0)
|
|
103
|
+
output.append(struct.unpack(f"<{str_len}s", response[str_len])[0])
|
|
104
|
+
response = response[str_len + 1:] #+1 to skip past the null terminator character too
|
|
105
|
+
else: #Fast parse because no strings
|
|
106
|
+
output.extend(struct.unpack(f"<{self.out_format}", response[:self.info.out_size]))
|
|
107
|
+
|
|
99
108
|
|
|
100
109
|
if self.info.num_out_params == 1:
|
|
101
110
|
return output[0]
|
|
102
111
|
return output
|
|
103
112
|
|
|
104
113
|
#Read the command dynamically from an input stream
|
|
105
|
-
def read_command(self, com: ThreespaceInputStream):
|
|
114
|
+
def read_command(self, com: ThreespaceInputStream, verbose=False):
|
|
106
115
|
raw = bytearray([])
|
|
107
116
|
if self.info.num_out_params == 0: return None, raw
|
|
108
117
|
output = []
|
|
@@ -113,14 +122,16 @@ class ThreespaceCommand:
|
|
|
113
122
|
response = com.read(size)
|
|
114
123
|
raw += response
|
|
115
124
|
if len(response) != size:
|
|
116
|
-
|
|
125
|
+
if verbose:
|
|
126
|
+
print(f"Failed to read {c} type. Aborting...")
|
|
117
127
|
return None
|
|
118
128
|
output.append(struct.unpack(format_str, response)[0])
|
|
119
129
|
else: #Strings are special, find the null terminator
|
|
120
130
|
response = com.read(1)
|
|
121
131
|
raw += response
|
|
122
132
|
if len(response) != 1:
|
|
123
|
-
|
|
133
|
+
if verbose:
|
|
134
|
+
print(f"Failed to read string. Aborting...")
|
|
124
135
|
return None
|
|
125
136
|
byte = chr(response[0])
|
|
126
137
|
string = ""
|
|
@@ -130,7 +141,8 @@ class ThreespaceCommand:
|
|
|
130
141
|
response = com.read(1)
|
|
131
142
|
raw += response
|
|
132
143
|
if len(response) != 1:
|
|
133
|
-
|
|
144
|
+
if verbose:
|
|
145
|
+
print(f"Failed to read string. Aborting...")
|
|
134
146
|
return None
|
|
135
147
|
byte = chr(response[0])
|
|
136
148
|
output.append(string)
|
|
@@ -150,6 +162,7 @@ class ThreespaceGetStreamingBatchCommand(ThreespaceCommand):
|
|
|
150
162
|
def set_stream_slots(self, streaming_slots: list[ThreespaceCommand]):
|
|
151
163
|
self.commands = streaming_slots
|
|
152
164
|
self.out_format = ''.join(slot.out_format for slot in streaming_slots if slot is not None)
|
|
165
|
+
self.info.out_size = struct.calcsize(f"<{self.out_format}")
|
|
153
166
|
|
|
154
167
|
def parse_response(self, response: bytes):
|
|
155
168
|
data = []
|
|
@@ -161,7 +174,7 @@ class ThreespaceGetStreamingBatchCommand(ThreespaceCommand):
|
|
|
161
174
|
|
|
162
175
|
return data
|
|
163
176
|
|
|
164
|
-
def read_command(self, com: ThreespaceInputStream):
|
|
177
|
+
def read_command(self, com: ThreespaceInputStream, verbose=False):
|
|
165
178
|
#Get the response to all the streaming commands
|
|
166
179
|
response = []
|
|
167
180
|
raw_response = bytearray([])
|
|
@@ -404,6 +417,7 @@ class StreamableCommands(Enum):
|
|
|
404
417
|
|
|
405
418
|
THREESPACE_AWAIT_COMMAND_FOUND = 0
|
|
406
419
|
THREESPACE_AWAIT_COMMAND_TIMEOUT = 1
|
|
420
|
+
THREESPACE_AWAIT_BOOTLOADER = 2
|
|
407
421
|
|
|
408
422
|
T = TypeVar('T')
|
|
409
423
|
|
|
@@ -450,7 +464,7 @@ class ThreespaceBootloaderInfo:
|
|
|
450
464
|
THREESPACE_REQUIRED_HEADER = THREESPACE_HEADER_ECHO_BIT | THREESPACE_HEADER_CHECKSUM_BIT | THREESPACE_HEADER_LENGTH_BIT
|
|
451
465
|
class ThreespaceSensor:
|
|
452
466
|
|
|
453
|
-
def __init__(self, com = None, timeout=2):
|
|
467
|
+
def __init__(self, com = None, timeout=2, verbose=False):
|
|
454
468
|
if com is None: #Default to attempting to use the serial com class if none is provided
|
|
455
469
|
com = ThreespaceSerialComClass
|
|
456
470
|
|
|
@@ -473,22 +487,15 @@ class ThreespaceSensor:
|
|
|
473
487
|
except:
|
|
474
488
|
raise ValueError("Failed to create default ThreespaceSerialComClass from parameter:", type(com), com)
|
|
475
489
|
|
|
476
|
-
self.
|
|
477
|
-
|
|
478
|
-
self.
|
|
479
|
-
|
|
480
|
-
self.
|
|
481
|
-
|
|
482
|
-
#Some commands are special and need added specially
|
|
483
|
-
if command.info.num == THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM:
|
|
484
|
-
self.getStreamingBatchCommand = ThreespaceGetStreamingBatchCommand([])
|
|
485
|
-
command = self.getStreamingBatchCommand
|
|
486
|
-
|
|
487
|
-
self.__add_command(command)
|
|
488
|
-
|
|
489
|
-
self.immediate_debug = False
|
|
490
|
+
self.immediate_debug = True #Assume it is on from the start. May cause it to take slightly longer to initialize, but prevents breaking if it is on
|
|
491
|
+
#Callback gives the debug message and sensor object that caused it
|
|
492
|
+
self.__debug_cache: list[str] = [] #Used for storing startup debug messages until sensor state is confirmed
|
|
493
|
+
|
|
494
|
+
self.verbose = verbose
|
|
495
|
+
self.debug_callback: Callable[[str, ThreespaceSensor],None] = self.__default_debug_callback
|
|
490
496
|
self.misaligned = False
|
|
491
497
|
self.dirty_cache = False
|
|
498
|
+
self.header_info = ThreespaceHeaderInfo()
|
|
492
499
|
self.header_enabled = True
|
|
493
500
|
|
|
494
501
|
#All the different streaming options
|
|
@@ -499,12 +506,27 @@ class ThreespaceSensor:
|
|
|
499
506
|
|
|
500
507
|
#Used to ensure connecting to the correct sensor when reconnecting
|
|
501
508
|
self.serial_number = None
|
|
509
|
+
self.short_serial_number = None
|
|
510
|
+
self.sensor_family = None
|
|
511
|
+
self.firmware_version = None
|
|
512
|
+
|
|
513
|
+
self.commands: list[ThreespaceCommand] = [None] * 256
|
|
514
|
+
self.getStreamingBatchCommand: ThreespaceGetStreamingBatchCommand = None
|
|
515
|
+
self.funcs = {}
|
|
502
516
|
|
|
503
517
|
self.__cached_in_bootloader = self.__check_bootloader_status()
|
|
504
518
|
if not self.in_bootloader:
|
|
505
519
|
self.__firmware_init()
|
|
506
520
|
else:
|
|
507
|
-
self.
|
|
521
|
+
self.__cache_serial_number(self.bootloader_get_sn())
|
|
522
|
+
self.__empty_debug_cache()
|
|
523
|
+
|
|
524
|
+
#Just a helper for outputting information
|
|
525
|
+
def log(self, *args):
|
|
526
|
+
if self.verbose:
|
|
527
|
+
print(*args)
|
|
528
|
+
|
|
529
|
+
#-----------------------INITIALIZIATION & REINITIALIZATION-----------------------------------
|
|
508
530
|
|
|
509
531
|
def __firmware_init(self):
|
|
510
532
|
"""
|
|
@@ -512,9 +534,13 @@ class ThreespaceSensor:
|
|
|
512
534
|
Called for powerup events when booting into firmware
|
|
513
535
|
"""
|
|
514
536
|
self.dirty_cache = False #No longer dirty cause initializing
|
|
515
|
-
|
|
516
|
-
self.com.read_all() #Clear anything that may be there
|
|
517
537
|
|
|
538
|
+
#Only reinitialize settings if detected firmware version changed (Or on startup)
|
|
539
|
+
version = self.get_settings("version_firmware")
|
|
540
|
+
if version != self.firmware_version:
|
|
541
|
+
self.firmware_version = version
|
|
542
|
+
self.__initialize_commands()
|
|
543
|
+
|
|
518
544
|
self.__reinit_firmware()
|
|
519
545
|
|
|
520
546
|
self.valid_mags = self.__get_valid_components("valid_mags")
|
|
@@ -522,16 +548,10 @@ class ThreespaceSensor:
|
|
|
522
548
|
self.valid_gyros = self.__get_valid_components("valid_gyros")
|
|
523
549
|
self.valid_baros = self.__get_valid_components("valid_baros")
|
|
524
550
|
|
|
525
|
-
def __get_valid_components(self, key: str):
|
|
526
|
-
valid = self.get_settings(key)
|
|
527
|
-
if len(valid) == 0: return []
|
|
528
|
-
return [int(v) for v in valid.split(',')]
|
|
529
|
-
|
|
530
551
|
def __reinit_firmware(self):
|
|
531
552
|
"""
|
|
532
553
|
Called when settings may have changed but a full reboot did not occur
|
|
533
554
|
"""
|
|
534
|
-
self.com.read_all() #Clear anything that may be there
|
|
535
555
|
self.dirty_cache = False #No longer dirty cause initializing
|
|
536
556
|
|
|
537
557
|
self.header_info = ThreespaceHeaderInfo()
|
|
@@ -543,26 +563,68 @@ class ThreespaceSensor:
|
|
|
543
563
|
self.file_stream_length = 0
|
|
544
564
|
|
|
545
565
|
self.streaming_packet_size = 0
|
|
546
|
-
self.header_enabled = True
|
|
547
566
|
self._force_stop_streaming()
|
|
548
567
|
|
|
549
568
|
#Now reinitialize the cached settings
|
|
550
569
|
self.__cache_header_settings()
|
|
551
|
-
self.
|
|
570
|
+
self.__cache_streaming_settings()
|
|
552
571
|
|
|
553
|
-
self.
|
|
572
|
+
self.__cache_serial_number(int(self.get_settings("serial_number"), 16))
|
|
573
|
+
self.__empty_debug_cache()
|
|
554
574
|
self.immediate_debug = int(self.get_settings("debug_mode")) == 1 #Needed for some startup processes when restarting
|
|
555
575
|
|
|
576
|
+
def __initialize_commands(self):
|
|
577
|
+
self.commands: list[ThreespaceCommand] = [None] * 256
|
|
578
|
+
self.getStreamingBatchCommand: ThreespaceGetStreamingBatchCommand = None
|
|
579
|
+
self.funcs = {}
|
|
580
|
+
|
|
581
|
+
valid_commands = self.get_settings("valid_commands")
|
|
582
|
+
if valid_commands == THREESPACE_GET_SETTINGS_ERROR_RESPONSE:
|
|
583
|
+
#Treat all commands as valid because firmware is too old to have this setting
|
|
584
|
+
valid_commands = list(range(256))
|
|
585
|
+
self.log("Please update firmware to a version that contains ?valid_commands")
|
|
586
|
+
else:
|
|
587
|
+
valid_commands = list(int(v) for v in valid_commands.split(','))
|
|
588
|
+
|
|
589
|
+
for command in _threespace_commands:
|
|
590
|
+
#Skip commands that are not valid for this sensor
|
|
591
|
+
if command.info.num not in valid_commands:
|
|
592
|
+
#Register as invalid.
|
|
593
|
+
setattr(self, command.info.name, self.__invalid_command)
|
|
594
|
+
continue
|
|
595
|
+
|
|
596
|
+
#Some commands are special and need added specially
|
|
597
|
+
if command.info.num == THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM:
|
|
598
|
+
self.getStreamingBatchCommand = ThreespaceGetStreamingBatchCommand([])
|
|
599
|
+
command = self.getStreamingBatchCommand
|
|
600
|
+
|
|
601
|
+
self.__add_command(command)
|
|
602
|
+
|
|
603
|
+
#------------------------------INITIALIZATION HELPERS--------------------------------------------
|
|
604
|
+
|
|
605
|
+
def __get_valid_components(self, key: str):
|
|
606
|
+
valid = self.get_settings(key)
|
|
607
|
+
if len(valid) == 0: return []
|
|
608
|
+
return [int(v) for v in valid.split(',')]
|
|
609
|
+
|
|
556
610
|
def __add_command(self, command: ThreespaceCommand):
|
|
557
611
|
if self.commands[command.info.num] != None:
|
|
558
|
-
|
|
612
|
+
self.log(f"Registering duplicate command: {command.info.num} {self.commands[command.info.num].info.name} {command.info.name}")
|
|
559
613
|
self.commands[command.info.num] = command
|
|
560
614
|
|
|
561
|
-
#
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
615
|
+
#This command type has special logic that requires its own function.
|
|
616
|
+
#Make that function be called instead of using the generic execute that gets built
|
|
617
|
+
method = None
|
|
618
|
+
if command.custom_func is not None:
|
|
619
|
+
method = types.MethodType(command.custom_func, self)
|
|
620
|
+
else:
|
|
621
|
+
#Build the actual method for executing the command
|
|
622
|
+
code = f"def {command.info.name}(self, *args):\n"
|
|
623
|
+
code += f" return self.execute_command(self.commands[{command.info.num}], *args)"
|
|
624
|
+
exec(code, globals(), self.funcs)
|
|
625
|
+
method = types.MethodType(self.funcs[command.info.name], self)
|
|
626
|
+
|
|
627
|
+
setattr(self, command.info.name, method)
|
|
566
628
|
|
|
567
629
|
def __get_command(self, command_name: str):
|
|
568
630
|
for command in self.commands:
|
|
@@ -570,10 +632,107 @@ class ThreespaceSensor:
|
|
|
570
632
|
if command.info.name == command_name:
|
|
571
633
|
return command
|
|
572
634
|
return None
|
|
635
|
+
|
|
636
|
+
def __attempt_rediscover_self(self):
|
|
637
|
+
"""
|
|
638
|
+
Trys to change the com class currently being used to be a detected
|
|
639
|
+
com class with the same serial number. Useful for re-enumeration, such as when
|
|
640
|
+
entering bootloader and using USB.
|
|
641
|
+
"""
|
|
642
|
+
for potential_com in self.com.auto_detect():
|
|
643
|
+
potential_com.open()
|
|
644
|
+
sensor = ThreespaceSensor(potential_com)
|
|
645
|
+
if sensor.serial_number == self.serial_number:
|
|
646
|
+
self.com = potential_com
|
|
647
|
+
return True
|
|
648
|
+
sensor.cleanup() #Handles closing the potential_com
|
|
649
|
+
return False
|
|
573
650
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
651
|
+
def __cache_header_settings(self):
|
|
652
|
+
"""
|
|
653
|
+
Should be called any time changes are made to the header. Will normally be called via the check_dirty/reinit
|
|
654
|
+
"""
|
|
655
|
+
result = self.get_settings("header")
|
|
656
|
+
header = int(result)
|
|
657
|
+
#API requires these bits to be enabled, so don't let them be disabled
|
|
658
|
+
required_header = header | THREESPACE_REQUIRED_HEADER
|
|
659
|
+
if header == self.header_info.bitfield and header == required_header: return #Nothing to update
|
|
660
|
+
|
|
661
|
+
#Don't allow the header to change while streaming
|
|
662
|
+
#This is to prevent a situation where the header for streaming and commands are different
|
|
663
|
+
#since streaming caches the header. This would cause an issue where the echo byte could be in seperate
|
|
664
|
+
#positions, causing a situation where parsing a command and streaming at the same time breaks since it thinks both are valid cmd echoes.
|
|
665
|
+
if self.is_streaming:
|
|
666
|
+
self.log("Preventing header change due to currently streaming")
|
|
667
|
+
self.set_settings(header=self.header_info.bitfield)
|
|
668
|
+
return
|
|
669
|
+
|
|
670
|
+
if required_header != header:
|
|
671
|
+
self.log(f"Forcing header checksum, echo, and length enabled")
|
|
672
|
+
self.set_settings(header=required_header)
|
|
673
|
+
return
|
|
674
|
+
|
|
675
|
+
#Current/New header is valid, so can cache it
|
|
676
|
+
self.header_info.bitfield = header
|
|
677
|
+
self.cmd_echo_byte_index = self.header_info.get_start_byte(THREESPACE_HEADER_ECHO_BIT) #Needed for cmd validation while streaming
|
|
678
|
+
|
|
679
|
+
def __cache_serial_number(self, serial_number: int):
|
|
680
|
+
"""
|
|
681
|
+
Doesn't actually retrieve the serial number, rather sets various properties based on the serial number
|
|
682
|
+
"""
|
|
683
|
+
self.serial_number = serial_number
|
|
684
|
+
|
|
685
|
+
#Short SN is the 32 bit version of the u64 serial number
|
|
686
|
+
#It is defined as the FamilyVersion (byte) << 24 | Incrementor (24 bits)
|
|
687
|
+
family = (self.serial_number & THREESPACE_SN_FAMILY_MSK) >> THREESPACE_SN_FAMILY_POS
|
|
688
|
+
incrementor = (self.serial_number & THREESPACE_SN_INCREMENTOR_MSK) >> THREESPACE_SN_INCREMENTOR_POS
|
|
689
|
+
self.short_serial_number = family << 24 | incrementor
|
|
690
|
+
self.sensor_family = THREESPACE_SN_FAMILY_TO_NAME.get(family)
|
|
691
|
+
if self.sensor_family is None:
|
|
692
|
+
self.log(f"Unknown Sensor Family detected, {family}")
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
#--------------------------------REINIT/DIRTY Helpers-----------------------------------------------
|
|
696
|
+
def set_cached_settings_dirty(self):
|
|
697
|
+
"""
|
|
698
|
+
Could be streaming settings, header settings...
|
|
699
|
+
Basically the sensor needs reinitialized
|
|
700
|
+
"""
|
|
701
|
+
self.dirty_cache = True
|
|
702
|
+
|
|
703
|
+
def check_dirty(self):
|
|
704
|
+
if not self.dirty_cache: return
|
|
705
|
+
if self.com.reenumerates and not self.com.check_open(): #Must check this, as could have transitioned from bootloader to firmware or vice versa and just needs re-opened/detected
|
|
706
|
+
success = self.__attempt_rediscover_self()
|
|
707
|
+
if not success:
|
|
708
|
+
raise RuntimeError("Sensor connection lost")
|
|
709
|
+
|
|
710
|
+
self._force_stop_streaming() #Can't be streaming when checking the dirty cache. If you want to stream, don't do things that cause the object to go dirty.
|
|
711
|
+
was_in_bootloader = self.__cached_in_bootloader
|
|
712
|
+
self.__cached_in_bootloader = self.__check_bootloader_status()
|
|
713
|
+
|
|
714
|
+
if was_in_bootloader and not self.__cached_in_bootloader: #Just Exited bootloader, need to fully reinit
|
|
715
|
+
self.__firmware_init()
|
|
716
|
+
elif not self.__cached_in_bootloader: #Was already in firmware, so only need to partially reinit
|
|
717
|
+
self.__reinit_firmware() #Partially init when just naturally dirty
|
|
718
|
+
self.dirty_cache = False
|
|
719
|
+
|
|
720
|
+
#-----------------------------------DEBUG COMMANDS---------------------------------------------------
|
|
721
|
+
def __default_debug_callback(self, msg: str, sensor: "ThreespaceSensor"):
|
|
722
|
+
if self.serial_number is None:
|
|
723
|
+
self.__debug_cache.append(msg.strip())
|
|
724
|
+
else:
|
|
725
|
+
print(f"DEBUG {hex(self.serial_number)}:", msg.strip())
|
|
726
|
+
|
|
727
|
+
def __empty_debug_cache(self):
|
|
728
|
+
for msg in self.__debug_cache:
|
|
729
|
+
print(f"DEBUG {hex(self.serial_number)}:", msg)
|
|
730
|
+
self.__debug_cache.clear()
|
|
731
|
+
|
|
732
|
+
def set_debug_callback(self, callback: Callable[[str, "ThreespaceSensor"], None]):
|
|
733
|
+
self.debug_callback = callback
|
|
734
|
+
|
|
735
|
+
#-----------------------------------------------BASE SETTINGS PROTOCOL------------------------------------------------
|
|
577
736
|
|
|
578
737
|
#Can't just do if "header" in string because log_header_enabled exists and doesn't actually require cacheing the header
|
|
579
738
|
HEADER_KEYS = ["header", "header_status", "header_timestamp", "header_echo", "header_checksum", "header_serial", "header_length"]
|
|
@@ -593,10 +752,18 @@ class ThreespaceSensor:
|
|
|
593
752
|
params.append(f"{key}={value}")
|
|
594
753
|
cmd = f"!{';'.join(params)}\n"
|
|
595
754
|
|
|
755
|
+
if len(cmd) > 2048:
|
|
756
|
+
self.log("Too many settings in one set_settings call. Max str length is 2048 but got", len(cmd))
|
|
757
|
+
return 0xFF, 0xFF
|
|
758
|
+
|
|
596
759
|
#For dirty check
|
|
597
760
|
keys = cmd[1:-1].split(';')
|
|
598
761
|
keys = [v.split('=')[0] for v in keys]
|
|
599
762
|
|
|
763
|
+
#Must enable this before sending the set so can properly handle reading the response
|
|
764
|
+
if "debug_mode=1" in cmd:
|
|
765
|
+
self.immediate_debug = True
|
|
766
|
+
|
|
600
767
|
#Send cmd
|
|
601
768
|
self.com.write(cmd.encode())
|
|
602
769
|
|
|
@@ -604,50 +771,18 @@ class ThreespaceSensor:
|
|
|
604
771
|
err = 3
|
|
605
772
|
num_successes = 0
|
|
606
773
|
|
|
607
|
-
|
|
608
|
-
if
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if b'\n' not in line:
|
|
620
|
-
break
|
|
621
|
-
|
|
622
|
-
try:
|
|
623
|
-
values = line.decode().strip()
|
|
624
|
-
values = values.split(',')
|
|
625
|
-
if len(values) != 2: break
|
|
626
|
-
err = int(values[0])
|
|
627
|
-
num_successes = int(values[1])
|
|
628
|
-
except: break
|
|
629
|
-
if err > 255 or num_successes > 255:
|
|
630
|
-
break
|
|
631
|
-
|
|
632
|
-
#Successfully got pass all the checks!
|
|
633
|
-
#Consume the buffer and continue
|
|
634
|
-
found_response = True
|
|
635
|
-
self.com.readline()
|
|
636
|
-
break
|
|
637
|
-
if found_response: break
|
|
638
|
-
while not self.updateStreaming(max_checks=1): pass #Wait for streaming to parse something!
|
|
639
|
-
else:
|
|
640
|
-
#When not streaming, way more straight forward
|
|
641
|
-
try:
|
|
642
|
-
response = self.com.readline()
|
|
643
|
-
response = response.decode().strip()
|
|
644
|
-
err, num_successes = response.split(',')
|
|
645
|
-
err = int(err)
|
|
646
|
-
num_successes = int(num_successes)
|
|
647
|
-
except:
|
|
648
|
-
print("Failed to parse set response:", response)
|
|
649
|
-
return err, num_successes
|
|
650
|
-
|
|
774
|
+
response = self.__await_set_settings(self.com.timeout)
|
|
775
|
+
if response == THREESPACE_AWAIT_COMMAND_TIMEOUT:
|
|
776
|
+
self.log("Failed to get set_settings response")
|
|
777
|
+
return err, num_successes
|
|
778
|
+
|
|
779
|
+
#Decode response
|
|
780
|
+
response = self.com.readline()
|
|
781
|
+
response = response.decode().strip()
|
|
782
|
+
err, num_successes = response.split(',')
|
|
783
|
+
err = int(err)
|
|
784
|
+
num_successes = int(num_successes)
|
|
785
|
+
|
|
651
786
|
#Handle updating state variables based on settings
|
|
652
787
|
#If the user modified the header, need to cache the settings so the API knows how to interpret responses
|
|
653
788
|
if "header" in cmd.lower(): #First do a quick check
|
|
@@ -655,13 +790,14 @@ class ThreespaceSensor:
|
|
|
655
790
|
self.__cache_header_settings()
|
|
656
791
|
|
|
657
792
|
if "stream_slots" in cmd.lower():
|
|
658
|
-
self.
|
|
793
|
+
self.__cache_streaming_settings()
|
|
659
794
|
|
|
660
|
-
|
|
795
|
+
#All the settings changed, just need to mark dirty
|
|
796
|
+
if any(v in keys for v in ("default", "reboot")):
|
|
661
797
|
self.set_cached_settings_dirty()
|
|
662
798
|
|
|
663
799
|
if err:
|
|
664
|
-
|
|
800
|
+
self.log(f"Err setting {cmd}: {err=} {num_successes=}")
|
|
665
801
|
return err, num_successes
|
|
666
802
|
|
|
667
803
|
def get_settings(self, *args: str) -> dict[str, str] | str:
|
|
@@ -672,91 +808,165 @@ class ThreespaceSensor:
|
|
|
672
808
|
self.com.write(cmd.encode())
|
|
673
809
|
|
|
674
810
|
keys = cmd[1:-1].split(';')
|
|
675
|
-
|
|
811
|
+
error_response_len = len(THREESPACE_GET_SETTINGS_ERROR_RESPONSE)
|
|
676
812
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
#Ex: get_settings("header", "all") would work
|
|
682
|
-
if self.is_streaming:
|
|
683
|
-
first_key = bytes(keys[0] + "=", 'ascii') #Add on the equals sign to try and make this less likely to conflict with binary data
|
|
684
|
-
possible_outputs = [(len(error_response), bytes(error_response, 'ascii')), (len(first_key), first_key)]
|
|
685
|
-
possible_outputs.sort() #Must try the smallest one first because if streaming is slow, may take a while for the data to fill pass the largest possible value
|
|
686
|
-
start_time = time.time()
|
|
687
|
-
while True:
|
|
688
|
-
if time.time() - start_time > self.com.timeout:
|
|
689
|
-
print("Timeout parsing get response")
|
|
690
|
-
return {}
|
|
691
|
-
found_response = False
|
|
692
|
-
for length, key in possible_outputs:
|
|
693
|
-
possible_response = self.com.peek(length)
|
|
694
|
-
if possible_response == key: #This the response, so break and parse
|
|
695
|
-
found_response = True
|
|
696
|
-
break
|
|
697
|
-
if found_response: break
|
|
698
|
-
while not self.updateStreaming(max_checks=1): pass #Wait for streaming to process something. May just advance due to invalid
|
|
813
|
+
min_resp_length = 0
|
|
814
|
+
for key in keys:
|
|
815
|
+
min_resp_length += min(len(key) + 1, error_response_len)
|
|
816
|
+
|
|
699
817
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
818
|
+
response = self.__await_get_settings(min_resp_length, timeout=self.com.timeout)
|
|
819
|
+
if response == THREESPACE_AWAIT_COMMAND_TIMEOUT:
|
|
820
|
+
self.log("Requested:", cmd)
|
|
821
|
+
self.log("Potential response:", self.com.peekline())
|
|
822
|
+
raise RuntimeError("Failed to receive get_settings response")
|
|
823
|
+
|
|
824
|
+
response = self.com.readline()
|
|
825
|
+
response = response.decode().strip().split(';')
|
|
708
826
|
|
|
709
827
|
#Build the response dict
|
|
710
828
|
response_dict = {}
|
|
711
829
|
for i, v in enumerate(response):
|
|
712
|
-
if v ==
|
|
713
|
-
response_dict[keys[i]] =
|
|
830
|
+
if v == THREESPACE_GET_SETTINGS_ERROR_RESPONSE:
|
|
831
|
+
response_dict[keys[i]] = THREESPACE_GET_SETTINGS_ERROR_RESPONSE
|
|
714
832
|
continue
|
|
715
833
|
try:
|
|
716
834
|
key, value = v.split('=')
|
|
717
835
|
response_dict[key] = value
|
|
718
836
|
except:
|
|
719
|
-
|
|
837
|
+
self.log("Failed to parse get value:", i, v, len(v))
|
|
720
838
|
|
|
721
839
|
#Format response
|
|
722
840
|
if len(response_dict) == 1:
|
|
723
841
|
return list(response_dict.values())[0]
|
|
724
842
|
return response_dict
|
|
725
843
|
|
|
726
|
-
|
|
727
|
-
self.check_dirty()
|
|
844
|
+
#-----------Base Settings Parsing----------------
|
|
728
845
|
|
|
729
|
-
|
|
730
|
-
|
|
846
|
+
def __await_set_settings(self, timeout=2):
|
|
847
|
+
start_time = time.time()
|
|
848
|
+
MINIMUM_LENGTH = len("0,0\r\n")
|
|
849
|
+
MAXIMUM_LENGTH = len("255,255\r\n")
|
|
731
850
|
|
|
732
|
-
while
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
raise RuntimeError(f"Failed to get response to command {cmd.info.name}")
|
|
851
|
+
while True:
|
|
852
|
+
remaining_time = timeout - (time.time() - start_time)
|
|
853
|
+
if remaining_time <= 0:
|
|
854
|
+
return THREESPACE_AWAIT_COMMAND_TIMEOUT
|
|
855
|
+
if self.com.length < MINIMUM_LENGTH: continue
|
|
856
|
+
|
|
857
|
+
possible_response = self.com.peekline()
|
|
858
|
+
if b'\r\n' not in possible_response: continue
|
|
741
859
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
860
|
+
if len(possible_response) < MINIMUM_LENGTH:
|
|
861
|
+
self.__internal_update(self.__try_peek_header())
|
|
862
|
+
continue
|
|
863
|
+
|
|
864
|
+
#Attempt to parse the line
|
|
865
|
+
values = possible_response.split(b',')
|
|
866
|
+
if len(values) != 2:
|
|
867
|
+
self.__internal_update(self.__try_peek_header())
|
|
868
|
+
continue
|
|
869
|
+
|
|
870
|
+
v1 = 0
|
|
871
|
+
v2 = 0
|
|
872
|
+
try:
|
|
873
|
+
v1 = int(values[0].decode())
|
|
874
|
+
v2 = int(values[0].decode())
|
|
875
|
+
except:
|
|
876
|
+
self.__internal_update(self.__try_peek_header())
|
|
877
|
+
continue
|
|
878
|
+
|
|
879
|
+
if v1 < 0 or v1 > 255 or v2 < 0 or v2 > 255:
|
|
880
|
+
self.__internal_update(self.__try_peek_header())
|
|
881
|
+
continue
|
|
882
|
+
|
|
883
|
+
self.misaligned = False
|
|
884
|
+
return THREESPACE_AWAIT_COMMAND_FOUND
|
|
885
|
+
|
|
886
|
+
def __await_get_settings(self, min_resp_length: int, timeout=2, check_bootloader=False):
|
|
887
|
+
start_time = time.time()
|
|
888
|
+
|
|
889
|
+
while True:
|
|
890
|
+
remaining_time = timeout - (time.time() - start_time)
|
|
891
|
+
if remaining_time <= 0:
|
|
892
|
+
return THREESPACE_AWAIT_COMMAND_TIMEOUT
|
|
893
|
+
|
|
894
|
+
if self.com.length < min_resp_length: continue
|
|
895
|
+
if check_bootloader and self.com.peek(2) == b'OK':
|
|
896
|
+
return THREESPACE_AWAIT_BOOTLOADER
|
|
897
|
+
|
|
898
|
+
possible_response = self.com.peekline()
|
|
899
|
+
if b'\r\n' not in possible_response: #failed to get newline
|
|
900
|
+
continue
|
|
901
|
+
|
|
902
|
+
if len(possible_response) < min_resp_length:
|
|
903
|
+
self.__internal_update(self.__try_peek_header())
|
|
904
|
+
continue
|
|
905
|
+
|
|
906
|
+
#Make sure the line is all ascii data
|
|
907
|
+
if not possible_response.isascii():
|
|
908
|
+
self.__internal_update(self.__try_peek_header())
|
|
909
|
+
continue
|
|
910
|
+
|
|
911
|
+
#Check to make sure each potential key conforms to the standard
|
|
912
|
+
key_value_pairs = possible_response.decode().split(';')
|
|
913
|
+
err = False
|
|
914
|
+
for kvp in key_value_pairs:
|
|
915
|
+
if kvp.strip() == THREESPACE_GET_SETTINGS_ERROR_RESPONSE: continue
|
|
916
|
+
split = kvp.split('=')
|
|
917
|
+
if len(split) != 2:
|
|
918
|
+
err = True
|
|
919
|
+
break
|
|
920
|
+
k, v = split
|
|
921
|
+
if any(c in k for c in THREESPACE_SETTING_KEY_INVALID_CHARS):
|
|
922
|
+
err = True
|
|
923
|
+
break
|
|
924
|
+
if err:
|
|
925
|
+
self.__internal_update(self.__try_peek_header())
|
|
926
|
+
continue
|
|
927
|
+
|
|
928
|
+
self.misaligned = False
|
|
929
|
+
return THREESPACE_AWAIT_COMMAND_FOUND
|
|
930
|
+
|
|
931
|
+
#---------------------------------BASE COMMAND PARSING--------------------------------------
|
|
932
|
+
def __try_peek_header(self):
|
|
933
|
+
"""
|
|
934
|
+
Attempts to retrieve a header from the com class immediately.
|
|
935
|
+
|
|
936
|
+
Returns
|
|
937
|
+
-------
|
|
938
|
+
The header retrieved, or None
|
|
939
|
+
"""
|
|
940
|
+
if not self.header_enabled: return None
|
|
941
|
+
if self.com.length < self.header_info.size: return None
|
|
942
|
+
header = self.com.peek(self.header_info.size)
|
|
943
|
+
if len(header) != self.header_info.size: return None
|
|
944
|
+
header = ThreespaceHeader.from_bytes(header, self.header_info)
|
|
945
|
+
return header
|
|
946
|
+
|
|
947
|
+
def __peek_checksum(self, header: ThreespaceHeader, max_data_length=4096):
|
|
948
|
+
"""
|
|
949
|
+
Using a header that contains the checksum and data length, calculate the checksum of the expected
|
|
950
|
+
data and verify if with the checksum in the header.
|
|
751
951
|
|
|
752
|
-
|
|
952
|
+
Params
|
|
953
|
+
------
|
|
954
|
+
header : The header to verify
|
|
955
|
+
max_data_length : The maximum size to allow from header_length. This should be set to avoid a corrupted header with an extremely large length causing a lockup/timeout
|
|
956
|
+
"""
|
|
753
957
|
header_len = len(header.raw_binary)
|
|
958
|
+
if header.length > max_data_length:
|
|
959
|
+
self.log("DATA TOO BIG:", header.length)
|
|
960
|
+
return False
|
|
754
961
|
data = self.com.peek(header_len + header.length)[header_len:]
|
|
755
962
|
if len(data) != header.length: return False
|
|
756
963
|
checksum = sum(data) % 256
|
|
757
964
|
return checksum == header.checksum
|
|
758
965
|
|
|
759
966
|
def __await_command(self, cmd: ThreespaceCommand, timeout=2):
|
|
967
|
+
#Header isn't enabled, nothing can do. Just pretend we found it
|
|
968
|
+
if not self.header_enabled: return THREESPACE_AWAIT_COMMAND_FOUND
|
|
969
|
+
|
|
760
970
|
start_time = time.time()
|
|
761
971
|
|
|
762
972
|
#Update the streaming until the result for this command is next in the buffer
|
|
@@ -765,179 +975,137 @@ class ThreespaceSensor:
|
|
|
765
975
|
return THREESPACE_AWAIT_COMMAND_TIMEOUT
|
|
766
976
|
|
|
767
977
|
#Get potential header
|
|
768
|
-
header = self.
|
|
769
|
-
if
|
|
978
|
+
header = self.__try_peek_header()
|
|
979
|
+
if header is None:
|
|
770
980
|
continue
|
|
771
981
|
|
|
772
|
-
#Check to see what this packet is a response to
|
|
773
|
-
header = ThreespaceHeader.from_bytes(header, self.header_info)
|
|
774
982
|
echo = header.echo
|
|
775
983
|
|
|
776
984
|
if echo == cmd.info.num: #Cmd matches
|
|
777
|
-
if self.__peek_checksum(header):
|
|
985
|
+
if self.__peek_checksum(header, max_data_length=cmd.info.out_size):
|
|
986
|
+
self.misaligned = False
|
|
778
987
|
return THREESPACE_AWAIT_COMMAND_FOUND
|
|
779
988
|
|
|
780
989
|
#Error in packet, go start realigning
|
|
781
990
|
if not self.misaligned:
|
|
782
|
-
|
|
991
|
+
self.log(f"Checksum mismatch for command {cmd.info.num}")
|
|
783
992
|
self.misaligned = True
|
|
784
993
|
self.com.read(1)
|
|
785
994
|
else:
|
|
786
995
|
#It wasn't a response to the command, so may be a response to some internal system
|
|
787
|
-
self.__internal_update(header)
|
|
996
|
+
self.__internal_update(header)
|
|
997
|
+
|
|
998
|
+
#------------------------------BASE INPUT PARSING--------------------------------------------
|
|
788
999
|
|
|
789
|
-
def __internal_update(self, header: ThreespaceHeader):
|
|
1000
|
+
def __internal_update(self, header: ThreespaceHeader = None):
|
|
790
1001
|
"""
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1002
|
+
Manages checking the datastream for asynchronous responses (Streaming, Immediate Debug Messages).
|
|
1003
|
+
If no data is found to match these responses, the data buffer will be considered corrupted/misaligned
|
|
1004
|
+
and start advancing 1 byte at a time until a message is retrieved.
|
|
1005
|
+
For this reason, if waiting for a synchronous command response, this should be only checked after confirming the data
|
|
1006
|
+
is not in response to any synchronously queued commands to avoid removing actual data bytes from the com class.
|
|
1007
|
+
|
|
1008
|
+
Parameters
|
|
1009
|
+
----------
|
|
1010
|
+
header : ThreespaceHeader
|
|
1011
|
+
The header to use for checking if streaming results exist. Can optionally leave None if don't want to check streaming responses.
|
|
1012
|
+
|
|
1013
|
+
Returns
|
|
1014
|
+
--------
|
|
1015
|
+
False : Misalignment
|
|
1016
|
+
True : Internal Data Found/Parsed
|
|
794
1017
|
"""
|
|
795
1018
|
checksum_match = False #Just for debugging
|
|
796
1019
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if
|
|
801
|
-
self.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1020
|
+
if header is not None:
|
|
1021
|
+
#NOTE: FOR THIS TO WORK IT IS REQUIRED THAT THE HEADER DOES NOT CHANGE WHILE STREAMING ANY FORM OF DATA.
|
|
1022
|
+
#IT IS UP TO THE API TO ENFORCE NOT ALLOWING HEADER CHANGES WHILE ANY OF THOSE THINGS ARE HAPPENING
|
|
1023
|
+
if self.is_data_streaming and header.echo == THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM: #Its a streaming packet, so update streaming
|
|
1024
|
+
if checksum_match := self.__peek_checksum(header, max_data_length=self.getStreamingBatchCommand.info.out_size):
|
|
1025
|
+
self.__update_base_streaming()
|
|
1026
|
+
self.misaligned = False
|
|
1027
|
+
return True
|
|
1028
|
+
elif self.is_log_streaming and header.echo == THREESPACE_FILE_READ_BYTES_COMMAND_NUM:
|
|
1029
|
+
if checksum_match := self.__peek_checksum(header, max_data_length=2560): #TODO: Confirm this number can be 2048 and then pound define it. Currently set to 2560 because I know that is big enough, but not optimal
|
|
1030
|
+
self.__update_log_streaming()
|
|
1031
|
+
self.misaligned = False
|
|
1032
|
+
return True
|
|
1033
|
+
elif self.is_file_streaming and header.echo == THREESPACE_FILE_READ_BYTES_COMMAND_NUM:
|
|
1034
|
+
if checksum_match := self.__peek_checksum(header, max_data_length=THREESPACE_FILE_STREAMING_MAX_PACKET_SIZE):
|
|
1035
|
+
self.__update_file_streaming()
|
|
1036
|
+
self.misaligned = False
|
|
1037
|
+
return True
|
|
1038
|
+
|
|
1039
|
+
#Debug messages are possible and there is enough data to potentially be a debug message
|
|
1040
|
+
#NOTE: Firmware should avoid putting more then one \r\n in a debug message as they will be treated as unprocessed/misaligned characters
|
|
1041
|
+
if self.immediate_debug and self.com.length >= 7:
|
|
1042
|
+
#This peek can't be blocking so peekline can't be used
|
|
1043
|
+
potential_message = self.com.peek(min(self.com.length, 27)) #27 is 20 digit timestamp + " Level:"
|
|
1044
|
+
if b"Level:" in potential_message: #There is a debug message somewhere in the data, must validate it is the next item
|
|
1045
|
+
level_index = potential_message.index(b" Level:")
|
|
1046
|
+
partial = potential_message[:level_index]
|
|
1047
|
+
#There should not be a newline until the end of the message, so it shouldn't be in partial
|
|
1048
|
+
if partial.isascii() and partial.decode('ascii').isnumeric() and b'\r\n' not in partial:
|
|
1049
|
+
message = self.com.readline() #Read out the whole message!
|
|
1050
|
+
self.debug_callback(message.decode('ascii'), self)
|
|
1051
|
+
self.misaligned = False
|
|
1052
|
+
return True
|
|
811
1053
|
|
|
812
1054
|
#The response didn't match any of the expected asynchronous streaming API responses, so assume a misalignment
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
self.
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
"""
|
|
823
|
-
if not self.is_streaming: return False
|
|
824
|
-
|
|
825
|
-
#I may need to make this have a max num bytes it will process before exiting to prevent locking up on slower machines
|
|
826
|
-
#due to streaming faster then the program runs
|
|
827
|
-
num_checks = 0
|
|
828
|
-
data_processed = False
|
|
829
|
-
while num_checks < max_checks:
|
|
830
|
-
if self.com.length < self.header_info.size:
|
|
831
|
-
return data_processed
|
|
832
|
-
|
|
833
|
-
#Get header
|
|
834
|
-
header = self.com.peek(self.header_info.size)
|
|
1055
|
+
if header is not None:
|
|
1056
|
+
msg = f"Possible Misalignment or corruption/debug message, header {header} raw {header.raw_binary} {[hex(v) for v in header.raw_binary]}" \
|
|
1057
|
+
f" Checksum match? {checksum_match}"
|
|
1058
|
+
#f"{self.com.peek(min(self.com.length, 10))}"
|
|
1059
|
+
else:
|
|
1060
|
+
msg = "Possible Misalignment or corruption/debug message"
|
|
1061
|
+
#self.log("Misaligned:", self.com.peek(1))
|
|
1062
|
+
self.__handle_misalignment(msg)
|
|
1063
|
+
return False
|
|
835
1064
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
self.
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
return data_processed
|
|
1065
|
+
def __handle_misalignment(self, message: str = None):
|
|
1066
|
+
if not self.misaligned and message is not None:
|
|
1067
|
+
self.log(message)
|
|
1068
|
+
self.misaligned = True
|
|
1069
|
+
self.com.read(1) #Because of expected misalignment, go through buffer 1 by 1 until realigned
|
|
843
1070
|
|
|
1071
|
+
#-----------------------------BASE COMMAND EXECUTION-------------------------------------
|
|
844
1072
|
|
|
845
|
-
def
|
|
846
|
-
if self.is_data_streaming: return
|
|
1073
|
+
def execute_command(self, cmd: ThreespaceCommand, *args):
|
|
847
1074
|
self.check_dirty()
|
|
848
|
-
self.streaming_packets.clear()
|
|
849
1075
|
|
|
850
|
-
|
|
1076
|
+
retries = 0
|
|
1077
|
+
MAX_RETRIES = 3
|
|
851
1078
|
|
|
852
|
-
|
|
1079
|
+
while retries < MAX_RETRIES:
|
|
1080
|
+
cmd.send_command(self.com, *args, header_enabled=self.header_enabled)
|
|
1081
|
+
result = self.__await_command(cmd)
|
|
1082
|
+
if result == THREESPACE_AWAIT_COMMAND_FOUND:
|
|
1083
|
+
break
|
|
1084
|
+
retries += 1
|
|
1085
|
+
|
|
1086
|
+
if retries == MAX_RETRIES:
|
|
1087
|
+
raise RuntimeError(f"Failed to get response to command {cmd.info.name}")
|
|
853
1088
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
859
|
-
else:
|
|
860
|
-
header = ThreespaceHeader()
|
|
861
|
-
self.is_data_streaming = True
|
|
862
|
-
return ThreespaceCmdResult(None, header)
|
|
863
|
-
|
|
864
|
-
def _force_stop_streaming(self):
|
|
865
|
-
"""
|
|
866
|
-
This function is used to stop streaming without validating it was streaming and ignoring any output of the
|
|
867
|
-
communication line. This is a destructive call that will lose data, but will gurantee stopping streaming
|
|
868
|
-
and leave the communication line in a clean state
|
|
869
|
-
"""
|
|
870
|
-
cached_header_enabled = self.header_enabled
|
|
871
|
-
cahched_dirty = self.dirty_cache
|
|
872
|
-
|
|
873
|
-
#Must set these to gurantee it doesn't try and parse a response from anything
|
|
874
|
-
self.dirty_cache = False
|
|
875
|
-
self.header_enabled = False #Keep off for the attempt at stop streaming since if in an invalid state, won't be able to get response
|
|
876
|
-
self.stopStreaming() #Just in case was streaming
|
|
877
|
-
self.fileStopStream()
|
|
878
|
-
|
|
879
|
-
#TODO: Change this to pause the data logging instead, then check the state and update
|
|
880
|
-
self.stopDataLogging()
|
|
881
|
-
|
|
882
|
-
#Restore
|
|
883
|
-
self.header_enabled = cached_header_enabled
|
|
884
|
-
self.dirty_cache = cahched_dirty
|
|
1089
|
+
return self.read_and_parse_command(cmd)
|
|
1090
|
+
|
|
1091
|
+
def __invalid_command(self, *args):
|
|
1092
|
+
raise NotImplementedError("This method is not available.")
|
|
885
1093
|
|
|
886
|
-
def
|
|
887
|
-
self.
|
|
888
|
-
cmd = self.commands[86]
|
|
889
|
-
cmd.send_command(self.com, header_enabled=self.header_enabled)
|
|
890
|
-
if self.header_enabled: #Header will be enabled while streaming, but this is useful for startup
|
|
891
|
-
self.__await_command(cmd)
|
|
1094
|
+
def read_and_parse_command(self, cmd: ThreespaceCommand):
|
|
1095
|
+
if self.header_enabled:
|
|
892
1096
|
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
893
1097
|
else:
|
|
894
1098
|
header = ThreespaceHeader()
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
self.com.read_all()
|
|
898
|
-
self.is_data_streaming = False
|
|
899
|
-
return ThreespaceCmdResult(None, header)
|
|
900
|
-
|
|
901
|
-
def set_cached_settings_dirty(self):
|
|
902
|
-
"""
|
|
903
|
-
Could be streaming settings, header settings...
|
|
904
|
-
Basically the sensor needs reinitialized
|
|
905
|
-
"""
|
|
906
|
-
self.dirty_cache = True
|
|
1099
|
+
result, raw = cmd.read_command(self.com, verbose=self.verbose)
|
|
1100
|
+
return ThreespaceCmdResult(result, header, data_raw_binary=raw)
|
|
907
1101
|
|
|
908
|
-
|
|
909
|
-
"""
|
|
910
|
-
Trys to change the com class currently being used to be a detected
|
|
911
|
-
com class with the same serial number. Useful for re-enumeration, such as when
|
|
912
|
-
entering bootloader and using USB
|
|
913
|
-
"""
|
|
914
|
-
for potential_com in self.com.auto_detect():
|
|
915
|
-
potential_com.open()
|
|
916
|
-
sensor = ThreespaceSensor(potential_com)
|
|
917
|
-
if sensor.serial_number == self.serial_number:
|
|
918
|
-
self.com = potential_com
|
|
919
|
-
return True
|
|
920
|
-
sensor.cleanup() #Handles closing the potential_com
|
|
921
|
-
return False
|
|
1102
|
+
#-----------------------------------BASE STREAMING COMMMANDS----------------------------------------------
|
|
922
1103
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
success = self.__attempt_rediscover_self()
|
|
927
|
-
if not success:
|
|
928
|
-
raise RuntimeError("Sensor connection lost")
|
|
929
|
-
|
|
930
|
-
self._force_stop_streaming() #Can't be streaming when checking the dirty cache. If you want to stream, don't do things that cause the object to go dirty.
|
|
931
|
-
was_in_bootloader = self.__cached_in_bootloader
|
|
932
|
-
self.__cached_in_bootloader = self.__check_bootloader_status()
|
|
933
|
-
|
|
934
|
-
if was_in_bootloader and not self.__cached_in_bootloader: #Just Exited bootloader, need to fully reinit
|
|
935
|
-
self.__firmware_init()
|
|
936
|
-
elif not self.__cached_in_bootloader: #Was already in firmware, so only need to partially reinit
|
|
937
|
-
self.__reinit_firmware() #Partially init when just naturally dirty
|
|
938
|
-
self.dirty_cache = False
|
|
1104
|
+
@property
|
|
1105
|
+
def is_streaming(self):
|
|
1106
|
+
return self.is_data_streaming or self.is_log_streaming or self.is_file_streaming
|
|
939
1107
|
|
|
940
|
-
def
|
|
1108
|
+
def __cache_streaming_settings(self):
|
|
941
1109
|
cached_slots: list[ThreespaceCommand] = []
|
|
942
1110
|
slots: str = self.get_settings("stream_slots")
|
|
943
1111
|
slots = slots.split(',')
|
|
@@ -954,32 +1122,21 @@ class ThreespaceSensor:
|
|
|
954
1122
|
if command == None: continue
|
|
955
1123
|
self.streaming_packet_size += command.info.out_size
|
|
956
1124
|
|
|
957
|
-
def
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
self.set_settings(header=self.header_info.bitfield)
|
|
973
|
-
return
|
|
974
|
-
|
|
975
|
-
if required_header != header:
|
|
976
|
-
print(f"Forcing header checksum, echo, and length enabled")
|
|
977
|
-
self.set_settings(header=required_header)
|
|
978
|
-
return
|
|
979
|
-
|
|
980
|
-
#Current/New header is valid, so can cache it
|
|
981
|
-
self.header_info.bitfield = header
|
|
982
|
-
self.cmd_echo_byte_index = self.header_info.get_start_byte(THREESPACE_HEADER_ECHO_BIT) #Needed for cmd validation while streaming
|
|
1125
|
+
def startStreaming(self) -> ThreespaceCmdResult[None]: ...
|
|
1126
|
+
def __startStreaming(self) -> ThreespaceCmdResult[None]:
|
|
1127
|
+
if self.is_data_streaming: return
|
|
1128
|
+
self.streaming_packets.clear()
|
|
1129
|
+
self.__cache_streaming_settings()
|
|
1130
|
+
|
|
1131
|
+
result = self.execute_command(self.commands[THREESPACE_START_STREAMING_COMMAND_NUM])
|
|
1132
|
+
self.is_data_streaming = True
|
|
1133
|
+
return result
|
|
1134
|
+
|
|
1135
|
+
def stopStreaming(self) -> ThreespaceCmdResult[None]: ...
|
|
1136
|
+
def __stopStreaming(self) -> ThreespaceCmdResult[None]:
|
|
1137
|
+
result = self.execute_command(self.commands[THREESPACE_STOP_STREAMING_COMMAND_NUM])
|
|
1138
|
+
self.is_data_streaming = False
|
|
1139
|
+
return result
|
|
983
1140
|
|
|
984
1141
|
def __update_base_streaming(self):
|
|
985
1142
|
"""
|
|
@@ -1000,44 +1157,85 @@ class ThreespaceSensor:
|
|
|
1000
1157
|
def clearStreamingPackets(self):
|
|
1001
1158
|
self.streaming_packets.clear()
|
|
1002
1159
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1160
|
+
#This is called for all streaming types
|
|
1161
|
+
def updateStreaming(self, max_checks=float('inf')):
|
|
1162
|
+
"""
|
|
1163
|
+
Returns true if any amount of data was processed whether valid or not. This is called for all streaming types.
|
|
1164
|
+
"""
|
|
1165
|
+
if not self.is_streaming: return False
|
|
1006
1166
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1167
|
+
#I may need to make this have a max num bytes it will process before exiting to prevent locking up on slower machines
|
|
1168
|
+
#due to streaming faster then the program runs
|
|
1169
|
+
num_checks = 0
|
|
1170
|
+
data_processed = False
|
|
1171
|
+
while num_checks < max_checks:
|
|
1172
|
+
if self.com.length < self.header_info.size:
|
|
1173
|
+
return data_processed
|
|
1174
|
+
|
|
1175
|
+
#Get header
|
|
1176
|
+
header = self.com.peek(self.header_info.size)
|
|
1177
|
+
|
|
1178
|
+
#Get the header and send it to the internal update
|
|
1179
|
+
header = ThreespaceHeader.from_bytes(header, self.header_info)
|
|
1180
|
+
self.__internal_update(header)
|
|
1181
|
+
data_processed = True #Internal update always processes data. Either reads a streaming message, or advances buffer due to misalignment
|
|
1182
|
+
num_checks += 1
|
|
1010
1183
|
|
|
1011
|
-
|
|
1012
|
-
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
1013
|
-
else:
|
|
1014
|
-
header = ThreespaceHeader()
|
|
1184
|
+
return data_processed
|
|
1015
1185
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1186
|
+
#This is more so used for initialization. Its a way of stopping streaming without having to worry about parsing.
|
|
1187
|
+
#That way it can clean up the data stream that won't match the expected state if not already configured.
|
|
1188
|
+
def _force_stop_streaming(self):
|
|
1189
|
+
"""
|
|
1190
|
+
This function attempts to stop all possible streaming without knowing anything about the state of the sensor.
|
|
1191
|
+
This includes trying to stop before any commands are even registered as valid. This is to ensure the sensor can properly
|
|
1192
|
+
start and recover from error conditions.
|
|
1019
1193
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1194
|
+
This will stop streaming without validating it was streaming and ignoring any output of the
|
|
1195
|
+
communication line. This is a destructive call that will lose data, but will gurantee stopping streaming
|
|
1196
|
+
and leave the communication line in a clean state.
|
|
1197
|
+
"""
|
|
1198
|
+
cached_header_enabled = self.header_enabled
|
|
1199
|
+
cahched_dirty = self.dirty_cache
|
|
1024
1200
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1201
|
+
#Must set these to gurantee it doesn't try and parse a response from anything since don't know the state of header
|
|
1202
|
+
self.dirty_cache = False
|
|
1203
|
+
self.header_enabled = False #Keep off for the attempt at stop streaming since if in an invalid state, won't be able to get response
|
|
1027
1204
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1205
|
+
#NOTE that commands are accessed directly from the global table instead of commands registered to this sensor object
|
|
1206
|
+
#since this sensor object may have yet to register these commands when calling force_stop_streaming
|
|
1207
|
+
|
|
1208
|
+
#Stop base Streaming
|
|
1209
|
+
self.execute_command(threespaceCommandGetByName("stopStreaming"))
|
|
1210
|
+
self.is_data_streaming = False
|
|
1211
|
+
|
|
1212
|
+
#Stop file streaming
|
|
1213
|
+
self.execute_command(threespaceCommandGetByName("fileStopStream"))
|
|
1214
|
+
self.is_file_streaming = False
|
|
1215
|
+
|
|
1216
|
+
#Stop logging streaming
|
|
1217
|
+
# #TODO: Change this to pause the data logging instead, then check the state and update
|
|
1218
|
+
self.execute_command(threespaceCommandGetByName("stopDataLogging"))
|
|
1219
|
+
self.is_log_streaming = False
|
|
1033
1220
|
|
|
1034
|
-
#
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1221
|
+
#Restore
|
|
1222
|
+
self.header_enabled = cached_header_enabled
|
|
1223
|
+
self.dirty_cache = cahched_dirty
|
|
1224
|
+
|
|
1225
|
+
#-------------------------------------FILE STREAMING----------------------------------------------
|
|
1038
1226
|
|
|
1227
|
+
def fileStartStream(self) -> ThreespaceCmdResult[int]: ...
|
|
1228
|
+
def __fileStartStream(self) -> ThreespaceCmdResult[int]:
|
|
1229
|
+
result = self.execute_command(self.__get_command("fileStartStream"))
|
|
1230
|
+
self.file_stream_length = result.data
|
|
1231
|
+
self.is_file_streaming = True
|
|
1232
|
+
return result
|
|
1233
|
+
|
|
1234
|
+
def fileStopStream(self) -> ThreespaceCmdResult[None]: ...
|
|
1235
|
+
def __fileStopStream(self) -> ThreespaceCmdResult[None]:
|
|
1236
|
+
result = self.execute_command(self.__get_command("fileStopStream"))
|
|
1039
1237
|
self.is_file_streaming = False
|
|
1040
|
-
return
|
|
1238
|
+
return result
|
|
1041
1239
|
|
|
1042
1240
|
def getFileStreamData(self):
|
|
1043
1241
|
to_return = self.file_stream_data.copy()
|
|
@@ -1055,52 +1253,32 @@ class ThreespaceSensor:
|
|
|
1055
1253
|
data = self.com.read(header.length)
|
|
1056
1254
|
self.file_stream_data += data
|
|
1057
1255
|
self.file_stream_length -= header.length
|
|
1058
|
-
if header.length <
|
|
1256
|
+
if header.length < THREESPACE_FILE_STREAMING_MAX_PACKET_SIZE or self.file_stream_length == 0: #File streaming sends in chunks of 512. If not 512, it must be the last packet
|
|
1059
1257
|
self.is_file_streaming = False
|
|
1060
1258
|
if self.file_stream_length != 0:
|
|
1061
|
-
|
|
1259
|
+
self.log(f"File streaming stopped due to last packet. However still expected {self.file_stream_length} more bytes.")
|
|
1062
1260
|
|
|
1063
|
-
|
|
1064
|
-
self.check_dirty()
|
|
1261
|
+
#----------------------------DATA LOGGING--------------------------------------
|
|
1065
1262
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1263
|
+
def startDataLogging(self) -> ThreespaceCmdResult[None]: ...
|
|
1264
|
+
def __startDataLogging(self) -> ThreespaceCmdResult[None]:
|
|
1265
|
+
self.__cache_streaming_settings()
|
|
1068
1266
|
|
|
1069
1267
|
#Must check whether streaming is being done alongside logging or not. Also configure required settings if it is
|
|
1070
1268
|
streaming = bool(int(self.get_settings("log_immediate_output")))
|
|
1071
1269
|
if streaming:
|
|
1072
1270
|
self.set_settings(log_immediate_output_header_enabled=1,
|
|
1073
1271
|
log_immediate_output_header_mode=THREESPACE_OUTPUT_MODE_BINARY) #Must have header enabled in the log messages for this to work and must use binary for the header
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
if self.header_enabled:
|
|
1077
|
-
self.__await_command(cmd)
|
|
1078
|
-
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
1079
|
-
else:
|
|
1080
|
-
header = ThreespaceHeader()
|
|
1081
|
-
|
|
1272
|
+
|
|
1273
|
+
result = self.execute_command(self.__get_command("startDataLogging"))
|
|
1082
1274
|
self.is_log_streaming = streaming
|
|
1083
|
-
return
|
|
1084
|
-
|
|
1085
|
-
def stopDataLogging(self) -> ThreespaceCmdResult[None]:
|
|
1086
|
-
self.check_dirty()
|
|
1087
|
-
|
|
1088
|
-
cmd = self.__get_command("__stopDataLogging")
|
|
1089
|
-
cmd.send_command(self.com, header_enabled=self.header_enabled)
|
|
1090
|
-
|
|
1091
|
-
if self.header_enabled: #Header will be enabled while streaming, but this is useful for startup
|
|
1092
|
-
self.__await_command(cmd)
|
|
1093
|
-
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
1094
|
-
else:
|
|
1095
|
-
header = ThreespaceHeader()
|
|
1096
|
-
#TODO: Remove me now that realignment exists and multiple things can be streaming at once
|
|
1097
|
-
if self.is_log_streaming:
|
|
1098
|
-
time.sleep(0.05)
|
|
1099
|
-
while self.com.length:
|
|
1100
|
-
self.com.read_all()
|
|
1275
|
+
return result
|
|
1101
1276
|
|
|
1277
|
+
def stopDataLogging(self) -> ThreespaceCmdResult[None]: ...
|
|
1278
|
+
def __stopDataLogging(self) -> ThreespaceCmdResult[None]:
|
|
1279
|
+
result = self.execute_command(self.__get_command("stopDataLogging"))
|
|
1102
1280
|
self.is_log_streaming = False
|
|
1103
|
-
return
|
|
1281
|
+
return result
|
|
1104
1282
|
|
|
1105
1283
|
def __update_log_streaming(self):
|
|
1106
1284
|
"""
|
|
@@ -1112,23 +1290,26 @@ class ThreespaceSensor:
|
|
|
1112
1290
|
data = self.com.read(header.length)
|
|
1113
1291
|
self.file_stream_data += data
|
|
1114
1292
|
|
|
1115
|
-
|
|
1293
|
+
#---------------------------------POWER STATE CHANGING COMMANDS & BOOTLOADER------------------------------------
|
|
1294
|
+
|
|
1295
|
+
def softwareReset(self): ...
|
|
1296
|
+
def __softwareReset(self):
|
|
1116
1297
|
self.check_dirty()
|
|
1117
|
-
cmd = self.commands[
|
|
1298
|
+
cmd = self.commands[THREESPACE_SOFTWARE_RESET_COMMAND_NUM]
|
|
1118
1299
|
cmd.send_command(self.com)
|
|
1119
1300
|
self.com.close()
|
|
1301
|
+
#TODO: Make this actually wait instead of an arbitrary sleep length
|
|
1120
1302
|
time.sleep(0.5) #Give it time to restart
|
|
1121
1303
|
self.com.open()
|
|
1122
|
-
if self.immediate_debug:
|
|
1123
|
-
time.sleep(2) #An additional 2 seconds to ensure can clear all debug messages
|
|
1124
|
-
self.com.read_all()
|
|
1125
1304
|
self.__firmware_init()
|
|
1126
1305
|
|
|
1127
|
-
def enterBootloader(self):
|
|
1306
|
+
def enterBootloader(self): ...
|
|
1307
|
+
def __enterBootloader(self):
|
|
1128
1308
|
if self.in_bootloader: return
|
|
1129
1309
|
|
|
1130
|
-
cmd = self.commands[
|
|
1310
|
+
cmd = self.commands[THREESPACE_ENTER_BOOTLOADER_COMMAND_NUM]
|
|
1131
1311
|
cmd.send_command(self.com)
|
|
1312
|
+
#TODO: Make this actually wait instead of an arbitrary sleep length
|
|
1132
1313
|
time.sleep(0.5) #Give it time to boot into bootloader
|
|
1133
1314
|
if self.com.reenumerates:
|
|
1134
1315
|
self.com.close()
|
|
@@ -1166,12 +1347,20 @@ class ThreespaceSensor:
|
|
|
1166
1347
|
#By then adding a ?UUU, that will trigger a <KEY_ERROR> if in firmware. So, can tell if in bootloader or firmware by checking for OK or <KEY_ERROR>
|
|
1167
1348
|
bootloader = False
|
|
1168
1349
|
self.com.write("UUU?UUU\n".encode())
|
|
1169
|
-
response = self.
|
|
1170
|
-
if
|
|
1171
|
-
|
|
1172
|
-
|
|
1350
|
+
response = self.__await_get_settings(2, check_bootloader=True)
|
|
1351
|
+
if response == THREESPACE_AWAIT_COMMAND_TIMEOUT:
|
|
1352
|
+
self.log("Requested Bootloader, Got:")
|
|
1353
|
+
self.log(self.com.peek(self.com.length))
|
|
1354
|
+
raise RuntimeError("Failed to discover bootloader or firmware.")
|
|
1355
|
+
if response == THREESPACE_AWAIT_BOOTLOADER:
|
|
1173
1356
|
bootloader = True
|
|
1174
|
-
|
|
1357
|
+
time.sleep(0.1) #Give time for all the OK responses to come in
|
|
1358
|
+
self.com.read_all() #Remove the rest of the OK responses or the rest of the <KEY_ERROR> response
|
|
1359
|
+
elif response == THREESPACE_AWAIT_COMMAND_FOUND:
|
|
1360
|
+
bootloader = False
|
|
1361
|
+
self.com.readline() #Clear the setting, no need to parse
|
|
1362
|
+
else:
|
|
1363
|
+
raise Exception("Failed to detect if in bootloader or firmware")
|
|
1175
1364
|
return bootloader
|
|
1176
1365
|
|
|
1177
1366
|
def bootloader_get_sn(self):
|
|
@@ -1191,11 +1380,6 @@ class ThreespaceSensor:
|
|
|
1191
1380
|
success = self.__attempt_rediscover_self()
|
|
1192
1381
|
if not success:
|
|
1193
1382
|
raise RuntimeError("Failed to reconnect to sensor in firmware")
|
|
1194
|
-
self.com.read_all() #If debug_mode=1, might be debug messages waiting
|
|
1195
|
-
if self.immediate_debug:
|
|
1196
|
-
print("Waiting longer before booting into firmware because immediate debug was enabled.")
|
|
1197
|
-
time.sleep(2)
|
|
1198
|
-
self.com.read_all()
|
|
1199
1383
|
in_bootloader = self.__check_bootloader_status()
|
|
1200
1384
|
if in_bootloader:
|
|
1201
1385
|
raise RuntimeError("Failed to exit bootloader")
|
|
@@ -1249,299 +1433,134 @@ class ThreespaceSensor:
|
|
|
1249
1433
|
self.fileStopStream()
|
|
1250
1434
|
if self.is_log_streaming:
|
|
1251
1435
|
self.stopDataLogging()
|
|
1252
|
-
|
|
1436
|
+
|
|
1437
|
+
#The sensor may or may not have this command registered. So just try it
|
|
1438
|
+
try:
|
|
1439
|
+
#May not be opened, but also not cacheing that so just attempt to close.
|
|
1440
|
+
self.closeFile()
|
|
1441
|
+
except: pass
|
|
1253
1442
|
self.com.close()
|
|
1254
1443
|
|
|
1255
1444
|
#-------------------------START ALL PROTOTYPES------------------------------------
|
|
1256
1445
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
def eeptsGetOldestStep(self) -> ThreespaceCmdResult[list]:
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
def
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
def
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
def
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
def
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
def
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
def
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
def
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
def
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
def
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
def
|
|
1294
|
-
raise NotImplementedError("This method is not available.")
|
|
1295
|
-
|
|
1296
|
-
def getTaredOrientationAsAxisAngles(self) -> ThreespaceCmdResult[list[float]]:
|
|
1297
|
-
raise NotImplementedError("This method is not available.")
|
|
1298
|
-
|
|
1299
|
-
def getTaredOrientationAsTwoVector(self) -> ThreespaceCmdResult[list[float]]:
|
|
1300
|
-
raise NotImplementedError("This method is not available.")
|
|
1301
|
-
|
|
1302
|
-
def getDifferenceQuaternion(self) -> ThreespaceCmdResult[list[float]]:
|
|
1303
|
-
raise NotImplementedError("This method is not available.")
|
|
1304
|
-
|
|
1305
|
-
def getUntaredOrientation(self) -> ThreespaceCmdResult[list[float]]:
|
|
1306
|
-
raise NotImplementedError("This method is not available.")
|
|
1307
|
-
|
|
1308
|
-
def getUntaredOrientationAsEulerAngles(self) -> ThreespaceCmdResult[list[float]]:
|
|
1309
|
-
raise NotImplementedError("This method is not available.")
|
|
1310
|
-
|
|
1311
|
-
def getUntaredOrientationAsRotationMatrix(self) -> ThreespaceCmdResult[list[float]]:
|
|
1312
|
-
raise NotImplementedError("This method is not available.")
|
|
1313
|
-
|
|
1314
|
-
def getUntaredOrientationAsAxisAngles(self) -> ThreespaceCmdResult[list[float]]:
|
|
1315
|
-
raise NotImplementedError("This method is not available.")
|
|
1316
|
-
|
|
1317
|
-
def getUntaredOrientationAsTwoVector(self) -> ThreespaceCmdResult[list[float]]:
|
|
1318
|
-
raise NotImplementedError("This method is not available.")
|
|
1319
|
-
|
|
1320
|
-
def commitSettings(self) -> ThreespaceCmdResult[None]:
|
|
1321
|
-
raise NotImplementedError("This method is not available.")
|
|
1322
|
-
|
|
1323
|
-
def getMotionlessConfidenceFactor(self) -> ThreespaceCmdResult[float]:
|
|
1324
|
-
raise NotImplementedError("This method is not available.")
|
|
1325
|
-
|
|
1326
|
-
def enableMSC(self) -> ThreespaceCmdResult[None]:
|
|
1327
|
-
raise NotImplementedError("This method is not available.")
|
|
1328
|
-
|
|
1329
|
-
def disableMSC(self) -> ThreespaceCmdResult[None]:
|
|
1330
|
-
raise NotImplementedError("This method is not available.")
|
|
1331
|
-
|
|
1332
|
-
def getNextDirectoryItem(self) -> ThreespaceCmdResult[list[int,str,int]]:
|
|
1333
|
-
raise NotImplementedError("This method is not available.")
|
|
1334
|
-
|
|
1335
|
-
def changeDirectory(self, path: str) -> ThreespaceCmdResult[None]:
|
|
1336
|
-
raise NotImplementedError("This method is not available.")
|
|
1337
|
-
|
|
1338
|
-
def openFile(self, path: str) -> ThreespaceCmdResult[None]:
|
|
1339
|
-
raise NotImplementedError("This method is not available.")
|
|
1340
|
-
|
|
1341
|
-
def closeFile(self) -> ThreespaceCmdResult[None]:
|
|
1342
|
-
raise NotImplementedError("This method is not available.")
|
|
1343
|
-
|
|
1344
|
-
def fileGetRemainingSize(self) -> ThreespaceCmdResult[int]:
|
|
1345
|
-
raise NotImplementedError("This method is not available.")
|
|
1346
|
-
|
|
1347
|
-
def fileReadLine(self) -> ThreespaceCmdResult[str]:
|
|
1348
|
-
raise NotImplementedError("This method is not available.")
|
|
1349
|
-
|
|
1350
|
-
def fileReadBytes(self, num_bytes: int) -> ThreespaceCmdResult[bytes]:
|
|
1446
|
+
#To actually see how commands work, look at __initialize_commands and __add_command
|
|
1447
|
+
#But basically, these are all just prototypes. Information about the commands is in the table
|
|
1448
|
+
#beneath here, and the API simply calls its execute_command function on the Command information objects defined.
|
|
1449
|
+
|
|
1450
|
+
def eeptsStart(self) -> ThreespaceCmdResult[None]: ...
|
|
1451
|
+
def eeptsStop(self) -> ThreespaceCmdResult[None]: ...
|
|
1452
|
+
def eeptsGetOldestStep(self) -> ThreespaceCmdResult[list]: ...
|
|
1453
|
+
def eeptsGetNewestStep(self) -> ThreespaceCmdResult[list]: ...
|
|
1454
|
+
def eeptsGetNumStepsAvailable(self) -> ThreespaceCmdResult[int]: ...
|
|
1455
|
+
def eeptsInsertGPS(self, latitude: float, longitude: float) -> ThreespaceCmdResult[None]: ...
|
|
1456
|
+
def eeptsAutoOffset(self) -> ThreespaceCmdResult[None]: ...
|
|
1457
|
+
def getRawGyroRate(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1458
|
+
def getRawAccelVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1459
|
+
def getRawMagVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1460
|
+
def getTaredOrientation(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1461
|
+
def getTaredOrientationAsEulerAngles(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1462
|
+
def getTaredOrientationAsRotationMatrix(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1463
|
+
def getTaredOrientationAsAxisAngles(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1464
|
+
def getTaredOrientationAsTwoVector(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1465
|
+
def getDifferenceQuaternion(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1466
|
+
def getUntaredOrientation(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1467
|
+
def getUntaredOrientationAsEulerAngles(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1468
|
+
def getUntaredOrientationAsRotationMatrix(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1469
|
+
def getUntaredOrientationAsAxisAngles(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1470
|
+
def getUntaredOrientationAsTwoVector(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1471
|
+
def commitSettings(self) -> ThreespaceCmdResult[None]: ...
|
|
1472
|
+
def getMotionlessConfidenceFactor(self) -> ThreespaceCmdResult[float]: ...
|
|
1473
|
+
def enableMSC(self) -> ThreespaceCmdResult[None]: ...
|
|
1474
|
+
def disableMSC(self) -> ThreespaceCmdResult[None]: ...
|
|
1475
|
+
def getNextDirectoryItem(self) -> ThreespaceCmdResult[list[int,str,int]]: ...
|
|
1476
|
+
def changeDirectory(self, path: str) -> ThreespaceCmdResult[None]: ...
|
|
1477
|
+
def openFile(self, path: str) -> ThreespaceCmdResult[None]: ...
|
|
1478
|
+
def closeFile(self) -> ThreespaceCmdResult[None]: ...
|
|
1479
|
+
def fileGetRemainingSize(self) -> ThreespaceCmdResult[int]: ...
|
|
1480
|
+
def fileReadLine(self) -> ThreespaceCmdResult[str]: ...
|
|
1481
|
+
def fileReadBytes(self, num_bytes: int) -> ThreespaceCmdResult[bytes]: ...
|
|
1482
|
+
def __fileReadBytes(self, num_bytes: int) -> ThreespaceCmdResult[bytes]:
|
|
1351
1483
|
self.check_dirty()
|
|
1352
1484
|
cmd = self.commands[THREESPACE_FILE_READ_BYTES_COMMAND_NUM]
|
|
1353
1485
|
cmd.send_command(self.com, num_bytes, header_enabled=self.header_enabled)
|
|
1354
1486
|
self.__await_command(cmd)
|
|
1355
1487
|
if self.header_enabled:
|
|
1356
1488
|
header = ThreespaceHeader.from_bytes(self.com.read(self.header_info.size), self.header_info)
|
|
1489
|
+
num_bytes = min(num_bytes, header.length) #Its possible for less bytes to be returned when an error occurs (EX: Reading from unopened file)
|
|
1357
1490
|
else:
|
|
1358
1491
|
header = ThreespaceHeader()
|
|
1359
1492
|
|
|
1360
1493
|
response = self.com.read(num_bytes)
|
|
1361
1494
|
return ThreespaceCmdResult(response, header, data_raw_binary=response)
|
|
1362
1495
|
|
|
1363
|
-
def deleteFile(self, path: str) -> ThreespaceCmdResult[None]:
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
def
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
def
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
def
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
def
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
def
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
def
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
def
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
def
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
def
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
def
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
def
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
def
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
def
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
def
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
def
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
def
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
def
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
def
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
def
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
def
|
|
1424
|
-
raise NotImplementedError("This method is not available.")
|
|
1425
|
-
|
|
1426
|
-
def getTemperatureCelsius(self) -> ThreespaceCmdResult[float]:
|
|
1427
|
-
raise NotImplementedError("This method is not available.")
|
|
1428
|
-
|
|
1429
|
-
def getTemperatureFahrenheit(self) -> ThreespaceCmdResult[float]:
|
|
1430
|
-
raise NotImplementedError("This method is not available.")
|
|
1431
|
-
|
|
1432
|
-
def getNormalizedGyroRate(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1433
|
-
raise NotImplementedError("This method is not available.")
|
|
1434
|
-
|
|
1435
|
-
def getNormalizedAccelVec(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1436
|
-
raise NotImplementedError("This method is not available.")
|
|
1437
|
-
|
|
1438
|
-
def getNormalizedMagVec(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1439
|
-
raise NotImplementedError("This method is not available.")
|
|
1440
|
-
|
|
1441
|
-
def getCorrectedGyroRate(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1442
|
-
raise NotImplementedError("This method is not available.")
|
|
1443
|
-
|
|
1444
|
-
def getCorrectedAccelVec(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1445
|
-
raise NotImplementedError("This method is not available.")
|
|
1446
|
-
|
|
1447
|
-
def getCorrectedMagVec(self, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1448
|
-
raise NotImplementedError("This method is not available.")
|
|
1449
|
-
|
|
1450
|
-
def enableMSC(self) -> ThreespaceCmdResult[None]:
|
|
1451
|
-
raise NotImplementedError("This method is not available.")
|
|
1452
|
-
|
|
1453
|
-
def disableMSC(self) -> ThreespaceCmdResult[None]:
|
|
1454
|
-
raise NotImplementedError("This method is not available.")
|
|
1455
|
-
|
|
1456
|
-
def getTimestamp(self) -> ThreespaceCmdResult[int]:
|
|
1457
|
-
raise NotImplementedError("This method is not available.")
|
|
1458
|
-
|
|
1459
|
-
def getBatteryVoltage(self) -> ThreespaceCmdResult[float]:
|
|
1460
|
-
raise NotImplementedError("This method is not available.")
|
|
1461
|
-
|
|
1462
|
-
def getBatteryPercent(self) -> ThreespaceCmdResult[int]:
|
|
1463
|
-
raise NotImplementedError("This method is not available.")
|
|
1464
|
-
|
|
1465
|
-
def getBatteryStatus(self) -> ThreespaceCmdResult[int]:
|
|
1466
|
-
raise NotImplementedError("This method is not available.")
|
|
1467
|
-
|
|
1468
|
-
def getGpsCoord(self) -> ThreespaceCmdResult[list[float]]:
|
|
1469
|
-
raise NotImplementedError("This method is not available.")
|
|
1470
|
-
|
|
1471
|
-
def getGpsAltitude(self) -> ThreespaceCmdResult[float]:
|
|
1472
|
-
raise NotImplementedError("This method is not available.")
|
|
1473
|
-
|
|
1474
|
-
def getGpsFixState(self) -> ThreespaceCmdResult[int]:
|
|
1475
|
-
raise NotImplementedError("This method is not available.")
|
|
1476
|
-
|
|
1477
|
-
def getGpsHdop(self) -> ThreespaceCmdResult[float]:
|
|
1478
|
-
raise NotImplementedError("This method is not available.")
|
|
1479
|
-
|
|
1480
|
-
def getGpsSatellites(self) -> ThreespaceCmdResult[int]:
|
|
1481
|
-
raise NotImplementedError("This method is not available.")
|
|
1482
|
-
|
|
1483
|
-
def getButtonState(self) -> ThreespaceCmdResult[int]:
|
|
1484
|
-
raise NotImplementedError("This method is not available.")
|
|
1485
|
-
|
|
1486
|
-
def correctRawGyroData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1487
|
-
raise NotImplementedError("This method is not available.")
|
|
1488
|
-
|
|
1489
|
-
def correctRawAccelData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1490
|
-
raise NotImplementedError("This method is not available.")
|
|
1491
|
-
|
|
1492
|
-
def correctRawMagData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]:
|
|
1493
|
-
raise NotImplementedError("This method is not available.")
|
|
1494
|
-
|
|
1495
|
-
def formatSd(self) -> ThreespaceCmdResult[None]:
|
|
1496
|
-
raise NotImplementedError("This method is not available.")
|
|
1497
|
-
|
|
1498
|
-
def setDateTime(self, year: int, month: int, day: int, hour: int, minute: int, second: int) -> ThreespaceCmdResult[None]:
|
|
1499
|
-
raise NotImplementedError("This method is not available.")
|
|
1500
|
-
|
|
1501
|
-
def getDateTime(self) -> ThreespaceCmdResult[list[int]]:
|
|
1502
|
-
raise NotImplementedError("This method is not available.")
|
|
1503
|
-
|
|
1504
|
-
def tareWithCurrentOrientation(self) -> ThreespaceCmdResult[None]:
|
|
1505
|
-
raise NotImplementedError("This method is not available.")
|
|
1506
|
-
|
|
1507
|
-
def setBaseTareWithCurrentOrientation(self) -> ThreespaceCmdResult[None]:
|
|
1508
|
-
raise NotImplementedError("This method is not available.")
|
|
1509
|
-
|
|
1510
|
-
def resetFilter(self) -> ThreespaceCmdResult[None]:
|
|
1511
|
-
raise NotImplementedError("This method is not available.")
|
|
1512
|
-
|
|
1513
|
-
def getNumDebugMessages(self) -> ThreespaceCmdResult[int]:
|
|
1514
|
-
raise NotImplementedError("This method is not available.")
|
|
1515
|
-
|
|
1516
|
-
def getOldestDebugMessage(self) -> ThreespaceCmdResult[str]:
|
|
1517
|
-
raise NotImplementedError("This method is not available.")
|
|
1518
|
-
|
|
1519
|
-
def beginPassiveAutoCalibration(self, enabled_bitfield: int) -> ThreespaceCmdResult[None]:
|
|
1520
|
-
raise NotImplementedError("This method is not available.")
|
|
1521
|
-
|
|
1522
|
-
def getActivePassiveAutoCalibration(self) -> ThreespaceCmdResult[int]:
|
|
1523
|
-
raise NotImplementedError("This method is not available.")
|
|
1524
|
-
|
|
1525
|
-
def beginActiveAutoCalibration(self) -> ThreespaceCmdResult[None]:
|
|
1526
|
-
raise NotImplementedError("This method is not available.")
|
|
1527
|
-
|
|
1528
|
-
def isActiveAutoCalibrationActive(self) -> ThreespaceCmdResult[int]:
|
|
1529
|
-
raise NotImplementedError("This method is not available.")
|
|
1530
|
-
|
|
1531
|
-
def getStreamingLabel(self, cmd_num: int) -> ThreespaceCmdResult[str]:
|
|
1532
|
-
raise NotImplementedError("This method is not available.")
|
|
1533
|
-
|
|
1534
|
-
def setCursor(self, cursor_index: int) -> ThreespaceCmdResult[None]:
|
|
1535
|
-
raise NotImplementedError("This method is not available.")
|
|
1536
|
-
|
|
1537
|
-
def getLastLogCursorInfo(self) -> ThreespaceCmdResult[tuple[int,str]]:
|
|
1538
|
-
raise NotImplementedError("This method is not available.")
|
|
1539
|
-
|
|
1540
|
-
def pauseLogStreaming(self, pause: bool) -> ThreespaceCmdResult[None]:
|
|
1541
|
-
raise NotImplementedError("This method is not available.")
|
|
1496
|
+
def deleteFile(self, path: str) -> ThreespaceCmdResult[None]: ...
|
|
1497
|
+
def getStreamingBatch(self) -> ThreespaceCmdResult[list]: ...
|
|
1498
|
+
def setOffsetWithCurrentOrientation(self) -> ThreespaceCmdResult[None]: ...
|
|
1499
|
+
def resetBaseOffset(self) -> ThreespaceCmdResult[None]: ...
|
|
1500
|
+
def setBaseOffsetWithCurrentOrientation(self) -> ThreespaceCmdResult[None]: ...
|
|
1501
|
+
def getTaredTwoVectorInSensorFrame(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1502
|
+
def getUntaredTwoVectorInSensorFrame(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1503
|
+
def getPrimaryBarometerPressure(self) -> ThreespaceCmdResult[float]: ...
|
|
1504
|
+
def getPrimaryBarometerAltitude(self) -> ThreespaceCmdResult[float]: ...
|
|
1505
|
+
def getBarometerAltitude(self, id: int) -> ThreespaceCmdResult[float]: ...
|
|
1506
|
+
def getBarometerPressure(self, id: int) -> ThreespaceCmdResult[float]: ...
|
|
1507
|
+
def getAllPrimaryNormalizedData(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1508
|
+
def getPrimaryNormalizedGyroRate(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1509
|
+
def getPrimaryNormalizedAccelVec(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1510
|
+
def getPrimaryNormalizedMagVec(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1511
|
+
def getAllPrimaryCorrectedData(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1512
|
+
def getPrimaryCorrectedGyroRate(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1513
|
+
def getPrimaryCorrectedAccelVec(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1514
|
+
def getPrimaryCorrectedMagVec(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1515
|
+
def getPrimaryGlobalLinearAccel(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1516
|
+
def getPrimaryLocalLinearAccel(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1517
|
+
def getTemperatureCelsius(self) -> ThreespaceCmdResult[float]: ...
|
|
1518
|
+
def getTemperatureFahrenheit(self) -> ThreespaceCmdResult[float]: ...
|
|
1519
|
+
def getNormalizedGyroRate(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1520
|
+
def getNormalizedAccelVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1521
|
+
def getNormalizedMagVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1522
|
+
def getCorrectedGyroRate(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1523
|
+
def getCorrectedAccelVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1524
|
+
def getCorrectedMagVec(self, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1525
|
+
def enableMSC(self) -> ThreespaceCmdResult[None]: ...
|
|
1526
|
+
def disableMSC(self) -> ThreespaceCmdResult[None]: ...
|
|
1527
|
+
def getTimestamp(self) -> ThreespaceCmdResult[int]: ...
|
|
1528
|
+
def getBatteryVoltage(self) -> ThreespaceCmdResult[float]: ...
|
|
1529
|
+
def getBatteryPercent(self) -> ThreespaceCmdResult[int]: ...
|
|
1530
|
+
def getBatteryStatus(self) -> ThreespaceCmdResult[int]: ...
|
|
1531
|
+
def getGpsCoord(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1532
|
+
def getGpsAltitude(self) -> ThreespaceCmdResult[float]: ...
|
|
1533
|
+
def getGpsFixState(self) -> ThreespaceCmdResult[int]: ...
|
|
1534
|
+
def getGpsHdop(self) -> ThreespaceCmdResult[float]: ...
|
|
1535
|
+
def getGpsSatellites(self) -> ThreespaceCmdResult[int]: ...
|
|
1536
|
+
def getButtonState(self) -> ThreespaceCmdResult[int]: ...
|
|
1537
|
+
def correctRawGyroData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1538
|
+
def correctRawAccelData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1539
|
+
def correctRawMagData(self, x: float, y: float, z: float, id: int) -> ThreespaceCmdResult[list[float]]: ...
|
|
1540
|
+
def formatSd(self) -> ThreespaceCmdResult[None]: ...
|
|
1541
|
+
def setDateTime(self, year: int, month: int, day: int, hour: int, minute: int, second: int) -> ThreespaceCmdResult[None]: ...
|
|
1542
|
+
def getDateTime(self) -> ThreespaceCmdResult[list[int]]: ...
|
|
1543
|
+
def tareWithCurrentOrientation(self) -> ThreespaceCmdResult[None]: ...
|
|
1544
|
+
def setBaseTareWithCurrentOrientation(self) -> ThreespaceCmdResult[None]: ...
|
|
1545
|
+
def resetFilter(self) -> ThreespaceCmdResult[None]: ...
|
|
1546
|
+
def getNumDebugMessages(self) -> ThreespaceCmdResult[int]: ...
|
|
1547
|
+
def getOldestDebugMessage(self) -> ThreespaceCmdResult[str]: ...
|
|
1548
|
+
def selfTest(self) -> ThreespaceCmdResult[int]: ...
|
|
1549
|
+
def beginPassiveAutoCalibration(self, enabled_bitfield: int) -> ThreespaceCmdResult[None]: ...
|
|
1550
|
+
def getActivePassiveAutoCalibration(self) -> ThreespaceCmdResult[int]: ...
|
|
1551
|
+
def beginActiveAutoCalibration(self) -> ThreespaceCmdResult[None]: ...
|
|
1552
|
+
def isActiveAutoCalibrationActive(self) -> ThreespaceCmdResult[int]: ...
|
|
1553
|
+
def getStreamingLabel(self, cmd_num: int) -> ThreespaceCmdResult[str]: ...
|
|
1554
|
+
def setCursor(self, cursor_index: int) -> ThreespaceCmdResult[None]: ...
|
|
1555
|
+
def getLastLogCursorInfo(self) -> ThreespaceCmdResult[tuple[int,str]]: ...
|
|
1556
|
+
def pauseLogStreaming(self, pause: bool) -> ThreespaceCmdResult[None]: ...
|
|
1542
1557
|
|
|
1543
1558
|
THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM = 84
|
|
1559
|
+
THREESPACE_START_STREAMING_COMMAND_NUM = 85
|
|
1560
|
+
THREESPACE_STOP_STREAMING_COMMAND_NUM = 86
|
|
1544
1561
|
THREESPACE_FILE_READ_BYTES_COMMAND_NUM = 177
|
|
1562
|
+
THREESPACE_SOFTWARE_RESET_COMMAND_NUM = 226
|
|
1563
|
+
THREESPACE_ENTER_BOOTLOADER_COMMAND_NUM = 229
|
|
1545
1564
|
|
|
1546
1565
|
#Acutal command definitions
|
|
1547
1566
|
_threespace_commands: list[ThreespaceCommand] = [
|
|
@@ -1609,8 +1628,8 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1609
1628
|
ThreespaceCommand("disableMSC", 58, "", ""),
|
|
1610
1629
|
|
|
1611
1630
|
ThreespaceCommand("formatSd", 59, "", ""),
|
|
1612
|
-
ThreespaceCommand("
|
|
1613
|
-
ThreespaceCommand("
|
|
1631
|
+
ThreespaceCommand("startDataLogging", 60, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__startDataLogging),
|
|
1632
|
+
ThreespaceCommand("stopDataLogging", 61, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__stopDataLogging),
|
|
1614
1633
|
|
|
1615
1634
|
ThreespaceCommand("setDateTime", 62, "Bbbbbb", ""),
|
|
1616
1635
|
ThreespaceCommand("getDateTime", 63, "", "Bbbbbb"),
|
|
@@ -1628,9 +1647,9 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1628
1647
|
ThreespaceCommand("eeptsAutoOffset", 74, "", ""),
|
|
1629
1648
|
|
|
1630
1649
|
ThreespaceCommand("getStreamingLabel", 83, "b", "S"),
|
|
1631
|
-
ThreespaceCommand("
|
|
1632
|
-
ThreespaceCommand("
|
|
1633
|
-
ThreespaceCommand("
|
|
1650
|
+
ThreespaceCommand("getStreamingBatch", THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM, "", "S"),
|
|
1651
|
+
ThreespaceCommand("startStreaming", THREESPACE_START_STREAMING_COMMAND_NUM, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__startStreaming),
|
|
1652
|
+
ThreespaceCommand("stopStreaming", THREESPACE_STOP_STREAMING_COMMAND_NUM, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__stopStreaming),
|
|
1634
1653
|
ThreespaceCommand("pauseLogStreaming", 87, "b", ""),
|
|
1635
1654
|
|
|
1636
1655
|
ThreespaceCommand("getTimestamp", 94, "", "U"),
|
|
@@ -1641,6 +1660,7 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1641
1660
|
ThreespaceCommand("resetFilter", 120, "", ""),
|
|
1642
1661
|
ThreespaceCommand("getNumDebugMessages", 126, "", "B"),
|
|
1643
1662
|
ThreespaceCommand("getOldestDebugMessage", 127, "", "S"),
|
|
1663
|
+
ThreespaceCommand("selfTest", 128, "", "u"),
|
|
1644
1664
|
|
|
1645
1665
|
ThreespaceCommand("beginPassiveAutoCalibration", 165, "b", ""),
|
|
1646
1666
|
ThreespaceCommand("getActivePassiveAutoCalibration", 166, "", "b"),
|
|
@@ -1654,11 +1674,11 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1654
1674
|
ThreespaceCommand("closeFile", 174, "", ""),
|
|
1655
1675
|
ThreespaceCommand("fileGetRemainingSize", 175, "", "U"),
|
|
1656
1676
|
ThreespaceCommand("fileReadLine", 176, "", "S"),
|
|
1657
|
-
ThreespaceCommand("
|
|
1677
|
+
ThreespaceCommand("fileReadBytes", THREESPACE_FILE_READ_BYTES_COMMAND_NUM, "B", "S", custom_func=ThreespaceSensor._ThreespaceSensor__fileReadBytes), #This has to be handled specially as the output is variable length BYTES not STRING
|
|
1658
1678
|
ThreespaceCommand("deleteFile", 178, "S", ""),
|
|
1659
1679
|
ThreespaceCommand("setCursor", 179, "U", ""),
|
|
1660
|
-
ThreespaceCommand("
|
|
1661
|
-
ThreespaceCommand("
|
|
1680
|
+
ThreespaceCommand("fileStartStream", 180, "", "U", custom_func=ThreespaceSensor._ThreespaceSensor__fileStartStream),
|
|
1681
|
+
ThreespaceCommand("fileStopStream", 181, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__fileStopStream),
|
|
1662
1682
|
|
|
1663
1683
|
ThreespaceCommand("getBatteryVoltage", 201, "", "f"),
|
|
1664
1684
|
ThreespaceCommand("getBatteryPercent", 202, "", "b"),
|
|
@@ -1671,8 +1691,8 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1671
1691
|
ThreespaceCommand("getGpsSatellites", 219, "", "b"),
|
|
1672
1692
|
|
|
1673
1693
|
ThreespaceCommand("commitSettings", 225, "", ""),
|
|
1674
|
-
ThreespaceCommand("
|
|
1675
|
-
ThreespaceCommand("
|
|
1694
|
+
ThreespaceCommand("softwareReset", THREESPACE_SOFTWARE_RESET_COMMAND_NUM, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__softwareReset),
|
|
1695
|
+
ThreespaceCommand("enterBootloader", THREESPACE_ENTER_BOOTLOADER_COMMAND_NUM, "", "", custom_func=ThreespaceSensor._ThreespaceSensor__enterBootloader),
|
|
1676
1696
|
|
|
1677
1697
|
ThreespaceCommand("getButtonState", 250, "", "b"),
|
|
1678
1698
|
]
|
|
@@ -1683,6 +1703,12 @@ def threespaceCommandGet(cmd_num: int):
|
|
|
1683
1703
|
return command
|
|
1684
1704
|
return None
|
|
1685
1705
|
|
|
1706
|
+
def threespaceCommandGetByName(name: str):
|
|
1707
|
+
for command in _threespace_commands:
|
|
1708
|
+
if command.info.name == name:
|
|
1709
|
+
return command
|
|
1710
|
+
return None
|
|
1711
|
+
|
|
1686
1712
|
def threespaceCommandGetInfo(cmd_num: int):
|
|
1687
1713
|
command = threespaceCommandGet(cmd_num)
|
|
1688
1714
|
if command is None: return None
|