yostlabs 2025.3.6__tar.gz → 2025.4.28__tar.gz
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-2025.3.6 → yostlabs-2025.4.28}/PKG-INFO +1 -1
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/pyproject.toml +1 -1
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/communication/ble.py +11 -4
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/communication/serial.py +5 -1
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/math/quaternion.py +5 -7
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/api.py +60 -23
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/.gitignore +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/embedded_2024_dec_20.xml +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_ble.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_commands.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_component_specific_settings_and_commands.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_firmware_upload.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_parsing_stored_binary.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_read_settings.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_streaming.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_streaming_manager.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/Examples/example_write_settings.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/LICENSE +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/README.md +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/__init__.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/communication/__init__.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/communication/base.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/math/__init__.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/math/vector.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/__init__.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/consts.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/eepts.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/utils/__init__.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/utils/calibration.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/utils/parser.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/utils/streaming.py +0 -0
- {yostlabs-2025.3.6 → yostlabs-2025.4.28}/src/yostlabs/tss3/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yostlabs
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.4.28
|
|
4
4
|
Summary: Python resources and API for 3Space sensors from Yost Labs Inc.
|
|
5
5
|
Project-URL: Homepage, https://yostlabs.com/
|
|
6
6
|
Project-URL: Repository, https://github.com/YostLabs/3SpacePythonPackage/tree/main
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "yostlabs"
|
|
7
7
|
#If uploading again on the same day, add a fourth number
|
|
8
|
-
version = "2025.
|
|
8
|
+
version = "2025.04.28"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Yost Labs Inc.", email="techsupport@yostlabs.com" },
|
|
11
11
|
{ name="Andy Riedlinger", email="techsupport@yostlabs.com" },
|
|
@@ -39,14 +39,14 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
39
39
|
error_on_disconnect : If trying to read while the sensor is disconnected, an exception will be generated. This may be undesirable \
|
|
40
40
|
if it is expected that the sensor will frequently go in and out of range and the user wishes to preserve data (such as streaming)
|
|
41
41
|
"""
|
|
42
|
-
self.event_loop = asyncio.new_event_loop()
|
|
43
42
|
bleak_options = { "timeout": discovery_timeout, "disconnected_callback": self.__on_disconnect }
|
|
44
43
|
if isinstance(ble, BleakClient): #Actual client
|
|
45
44
|
self.client = ble
|
|
46
45
|
self.__name = ble.address
|
|
47
46
|
elif isinstance(ble, str):
|
|
48
47
|
if discover_name: #Local Name stirng
|
|
49
|
-
|
|
48
|
+
self.__lazy_init_scanner()
|
|
49
|
+
device = ThreespaceBLEComClass.SCANNER_EVENT_LOOP.run_until_complete(BleakScanner.find_device_by_name(ble, timeout=discovery_timeout))
|
|
50
50
|
if device is None:
|
|
51
51
|
raise BleakDeviceNotFoundError(ble)
|
|
52
52
|
self.client = BleakClient(device, **bleak_options)
|
|
@@ -63,7 +63,8 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
63
63
|
self.__timeout = self.DEFAULT_TIMEOUT
|
|
64
64
|
|
|
65
65
|
self.buffer = bytearray()
|
|
66
|
-
self.
|
|
66
|
+
self.event_loop: asyncio.AbstractEventLoop = None
|
|
67
|
+
self.data_read_event: asyncio.Event = None
|
|
67
68
|
|
|
68
69
|
#Default to 20, will update on open
|
|
69
70
|
self.max_packet_size = 20
|
|
@@ -91,6 +92,8 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
91
92
|
if not self.__connected and self.error_on_disconnect:
|
|
92
93
|
self.close()
|
|
93
94
|
return
|
|
95
|
+
self.event_loop = asyncio.new_event_loop()
|
|
96
|
+
self.data_read_event = asyncio.Event()
|
|
94
97
|
self.event_loop.run_until_complete(self.__async_open())
|
|
95
98
|
self.max_packet_size = self.client.mtu_size - 3 #-3 to account for the opcode and attribute handle stored in the data packet
|
|
96
99
|
self.__opened = True
|
|
@@ -106,6 +109,9 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
106
109
|
if not self.__opened: return
|
|
107
110
|
self.event_loop.run_until_complete(self.__async_close())
|
|
108
111
|
self.buffer.clear()
|
|
112
|
+
self.event_loop.close()
|
|
113
|
+
self.event_loop = None
|
|
114
|
+
self.data_read_event = None
|
|
109
115
|
self.__opened = False
|
|
110
116
|
|
|
111
117
|
def __on_disconnect(self, client: BleakClient):
|
|
@@ -138,6 +144,7 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
138
144
|
|
|
139
145
|
def __read_all_data(self):
|
|
140
146
|
self.event_loop.run_until_complete(self.__wait_for_callbacks_async())
|
|
147
|
+
self.data_read_event.clear()
|
|
141
148
|
self.__assert_connected()
|
|
142
149
|
|
|
143
150
|
def __on_data_received(self, sender: BleakGATTCharacteristic, data: bytearray):
|
|
@@ -153,10 +160,10 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
153
160
|
|
|
154
161
|
async def __await_read(self, timeout_time: int):
|
|
155
162
|
self.__assert_connected()
|
|
156
|
-
self.data_read_event.clear()
|
|
157
163
|
try:
|
|
158
164
|
async with async_timeout.timeout_at(timeout_time):
|
|
159
165
|
await self.data_read_event.wait()
|
|
166
|
+
self.data_read_event.clear()
|
|
160
167
|
return True
|
|
161
168
|
except:
|
|
162
169
|
return False
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from yostlabs.communication.base import *
|
|
2
2
|
import serial
|
|
3
3
|
import serial.tools.list_ports
|
|
4
|
+
import time
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class ThreespaceSerialComClass(ThreespaceComClass):
|
|
@@ -107,7 +108,10 @@ class ThreespaceSerialComClass(ThreespaceComClass):
|
|
|
107
108
|
|
|
108
109
|
@timeout.setter
|
|
109
110
|
def timeout(self, timeout: float):
|
|
110
|
-
self.ser.timeout = timeout
|
|
111
|
+
self.ser.timeout = timeout
|
|
112
|
+
#There is a bug in Windows drivers that requires a delay after setting timeout
|
|
113
|
+
#When using certain Serial Interfaces
|
|
114
|
+
time.sleep(0.01)
|
|
111
115
|
|
|
112
116
|
@property
|
|
113
117
|
def reenumerates(self) -> bool:
|
|
@@ -12,16 +12,15 @@ def quat_mul(a: list[float], b: list[float]):
|
|
|
12
12
|
|
|
13
13
|
#Rotates quaternion b by quaternion a, it does not combine them
|
|
14
14
|
def quat_rotate(a: list[float], b: list[float]):
|
|
15
|
-
inv = a
|
|
16
|
-
inv[0] *= -1
|
|
17
|
-
inv[1] *= -1
|
|
18
|
-
inv[2] *= -1
|
|
15
|
+
inv = quat_inverse(a)
|
|
19
16
|
axis = [b[0], b[1], b[2], 0]
|
|
20
17
|
halfway = quat_mul(a, axis)
|
|
21
18
|
final = quat_mul(halfway, inv)
|
|
22
19
|
return [*final[:3], b[3]]
|
|
23
20
|
|
|
24
21
|
def quat_inverse(quat: list[float]):
|
|
22
|
+
#Note: While technically negating just the W is rotationally equivalent, this is not a good idea
|
|
23
|
+
#as it will conflict with rotating vectors, which are not rotations, by quaternions
|
|
25
24
|
return [-quat[0], -quat[1], -quat[2], quat[3]]
|
|
26
25
|
|
|
27
26
|
def quat_rotate_vec(quat: list[float], vec: list[float]):
|
|
@@ -43,7 +42,6 @@ def angles_to_quaternion(angles: list[float], order: str, degrees=True):
|
|
|
43
42
|
imaginary = math.sin(angle / 2)
|
|
44
43
|
unit_vec = [v * imaginary for v in unit_vec]
|
|
45
44
|
angle_quat = [*unit_vec, w]
|
|
46
|
-
#print(f"{axis} {angle} {angle_quat}")
|
|
47
45
|
quat = quat_mul(quat, angle_quat)
|
|
48
46
|
return quat
|
|
49
47
|
|
|
@@ -160,8 +158,8 @@ def quaternion_swap_axes_fast(quat: list, old_parsed_order: list[list, list, boo
|
|
|
160
158
|
|
|
161
159
|
if old_right_handed != new_right_handed:
|
|
162
160
|
#Different handed systems rotate opposite directions. So to maintain the same rotation,
|
|
163
|
-
#
|
|
164
|
-
new_quat
|
|
161
|
+
#invert the quaternion
|
|
162
|
+
new_quat = quat_inverse(new_quat)
|
|
165
163
|
|
|
166
164
|
return new_quat
|
|
167
165
|
|
|
@@ -471,16 +471,18 @@ class ThreespaceSensor:
|
|
|
471
471
|
def __init__(self, com = None, timeout=2, verbose=False, initial_clear_timeout=None):
|
|
472
472
|
if com is None: #Default to attempting to use the serial com class if none is provided
|
|
473
473
|
com = ThreespaceSerialComClass
|
|
474
|
-
|
|
474
|
+
self.verbose = verbose
|
|
475
|
+
|
|
475
476
|
manually_opened_com = False
|
|
476
477
|
#Auto discover using the supplied com class type
|
|
477
478
|
if inspect.isclass(com) and issubclass(com, ThreespaceComClass):
|
|
478
479
|
new_com = None
|
|
480
|
+
self.log("Auto-Discovering Sensor")
|
|
479
481
|
for serial_com in com.auto_detect():
|
|
480
482
|
new_com = serial_com
|
|
481
483
|
break #Exit after getting 1
|
|
482
484
|
if new_com is None:
|
|
483
|
-
raise RuntimeError("Failed to auto discover com port")
|
|
485
|
+
raise RuntimeError("Failed to auto discover com port")
|
|
484
486
|
self.com = new_com
|
|
485
487
|
manually_opened_com = True
|
|
486
488
|
self.com.open()
|
|
@@ -496,11 +498,13 @@ class ThreespaceSensor:
|
|
|
496
498
|
except:
|
|
497
499
|
raise ValueError("Failed to create default ThreespaceSerialComClass from parameter:", type(com), com)
|
|
498
500
|
|
|
501
|
+
self.restart_delay = 0.5
|
|
502
|
+
|
|
503
|
+
self.log("Configuring sensor communication")
|
|
499
504
|
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
|
|
500
505
|
#Callback gives the debug message and sensor object that caused it
|
|
501
506
|
self.__debug_cache: list[str] = [] #Used for storing startup debug messages until sensor state is confirmed
|
|
502
507
|
|
|
503
|
-
self.verbose = verbose
|
|
504
508
|
self.debug_callback: Callable[[str, ThreespaceSensor],None] = self.__default_debug_callback
|
|
505
509
|
self.misaligned = False
|
|
506
510
|
self.dirty_cache = False
|
|
@@ -511,11 +515,13 @@ class ThreespaceSensor:
|
|
|
511
515
|
self.is_data_streaming = False
|
|
512
516
|
self.is_log_streaming = False
|
|
513
517
|
self.is_file_streaming = False
|
|
518
|
+
self.log("Stopping potential streaming")
|
|
514
519
|
self._force_stop_streaming()
|
|
515
520
|
#Clear out the buffer to allow faster initializing
|
|
516
521
|
#Ex: If a large buffer build up due to streaming, especially if using a slower interface like BLE,
|
|
517
522
|
#it may take a while before the entire garbage data can be parsed when checking for bootloader, causing a timeout
|
|
518
|
-
#even though it would have eventually succeeded
|
|
523
|
+
#even though it would have eventually succeeded
|
|
524
|
+
self.log("Clearing com")
|
|
519
525
|
self.__clear_com(initial_clear_timeout)
|
|
520
526
|
|
|
521
527
|
|
|
@@ -529,19 +535,24 @@ class ThreespaceSensor:
|
|
|
529
535
|
self.getStreamingBatchCommand: ThreespaceGetStreamingBatchCommand = None
|
|
530
536
|
self.funcs = {}
|
|
531
537
|
|
|
538
|
+
self.log("Checking firmware status")
|
|
532
539
|
try:
|
|
533
540
|
self.__cached_in_bootloader = self.__check_bootloader_status()
|
|
534
541
|
if not self.in_bootloader:
|
|
542
|
+
self.log("Initializing firmware")
|
|
535
543
|
self.__firmware_init()
|
|
536
544
|
else:
|
|
545
|
+
self.log("Initializing bootloader")
|
|
537
546
|
self.__cache_serial_number(self.bootloader_get_sn())
|
|
538
547
|
self.__empty_debug_cache()
|
|
539
548
|
#This is just to prevent a situation where instantiating the API creates and fails to release a com class on failure when user catches the exception
|
|
540
549
|
#If user provides the com class, it is up to them to handle its state on error
|
|
541
550
|
except Exception as e:
|
|
551
|
+
self.log("Failed to initialize sensor")
|
|
542
552
|
if manually_opened_com:
|
|
543
553
|
self.com.close()
|
|
544
554
|
raise e
|
|
555
|
+
self.log("Successfully initialized sensor")
|
|
545
556
|
|
|
546
557
|
#Just a helper for outputting information
|
|
547
558
|
def log(self, *args):
|
|
@@ -554,6 +565,7 @@ class ThreespaceSensor:
|
|
|
554
565
|
data = self.com.read_all()
|
|
555
566
|
if refresh_timeout is None: return
|
|
556
567
|
while len(data) > 0: #Continue until all data is cleared
|
|
568
|
+
self.log(f"Refresh clear Length: {len(data)}")
|
|
557
569
|
start_time = time.time()
|
|
558
570
|
while time.time() - start_time < refresh_timeout: #Wait up to refresh time for a new message
|
|
559
571
|
data = self.com.read_all()
|
|
@@ -651,8 +663,8 @@ class ThreespaceSensor:
|
|
|
651
663
|
method = types.MethodType(command.custom_func, self)
|
|
652
664
|
else:
|
|
653
665
|
#Build the actual method for executing the command
|
|
654
|
-
code = f"def {command.info.name}(self, *args):\n"
|
|
655
|
-
code += f" return self.execute_command(self.commands[{command.info.num}], *args)"
|
|
666
|
+
code = f"def {command.info.name}(self, *args, **kwargs):\n"
|
|
667
|
+
code += f" return self.execute_command(self.commands[{command.info.num}], *args, **kwargs)"
|
|
656
668
|
exec(code, globals(), self.funcs)
|
|
657
669
|
method = types.MethodType(self.funcs[command.info.name], self)
|
|
658
670
|
|
|
@@ -766,6 +778,17 @@ class ThreespaceSensor:
|
|
|
766
778
|
|
|
767
779
|
#-----------------------------------------------BASE SETTINGS PROTOCOL------------------------------------------------
|
|
768
780
|
|
|
781
|
+
#Helper for converting python types to strings that set_settings can understand
|
|
782
|
+
def __internal_str(self, value):
|
|
783
|
+
if isinstance(value, float):
|
|
784
|
+
return f"{value:.10f}"
|
|
785
|
+
elif isinstance(value, bool):
|
|
786
|
+
return int(value)
|
|
787
|
+
elif isinstance(value, Enum):
|
|
788
|
+
return str(value.value)
|
|
789
|
+
else:
|
|
790
|
+
return str(value)
|
|
791
|
+
|
|
769
792
|
#Can't just do if "header" in string because log_header_enabled exists and doesn't actually require cacheing the header
|
|
770
793
|
HEADER_KEYS = ["header", "header_status", "header_timestamp", "header_echo", "header_checksum", "header_serial", "header_length"]
|
|
771
794
|
def set_settings(self, param_string: str = None, **kwargs):
|
|
@@ -777,10 +800,10 @@ class ThreespaceSensor:
|
|
|
777
800
|
|
|
778
801
|
for key, value in kwargs.items():
|
|
779
802
|
if isinstance(value, list):
|
|
780
|
-
value = [
|
|
803
|
+
value = [self.__internal_str(v) for v in value]
|
|
781
804
|
value = ','.join(value)
|
|
782
|
-
|
|
783
|
-
value =
|
|
805
|
+
else:
|
|
806
|
+
value = self.__internal_str(value)
|
|
784
807
|
params.append(f"{key}={value}")
|
|
785
808
|
cmd = f"!{';'.join(params)}\n"
|
|
786
809
|
|
|
@@ -996,11 +1019,18 @@ class ThreespaceSensor:
|
|
|
996
1019
|
"""
|
|
997
1020
|
header_len = len(header.raw_binary)
|
|
998
1021
|
if header.length > max_data_length:
|
|
999
|
-
self.
|
|
1022
|
+
if not self.misaligned:
|
|
1023
|
+
self.log("DATA TOO BIG:", header.length)
|
|
1000
1024
|
return False
|
|
1001
1025
|
data = self.com.peek(header_len + header.length)[header_len:]
|
|
1002
|
-
if len(data) != header.length:
|
|
1026
|
+
if len(data) != header.length:
|
|
1027
|
+
if not self.misaligned:
|
|
1028
|
+
self.log(f"Data Length Mismatch - Got: {len(data)} Expected: {header.length}")
|
|
1029
|
+
return False
|
|
1003
1030
|
checksum = sum(data) % 256
|
|
1031
|
+
if checksum != header.checksum and not self.misaligned:
|
|
1032
|
+
self.log(f"Checksum Mismatch - Got: {checksum} Expected: {header.checksum}")
|
|
1033
|
+
self.log(f"Data: {data}")
|
|
1004
1034
|
return checksum == header.checksum
|
|
1005
1035
|
|
|
1006
1036
|
def __await_command(self, cmd: ThreespaceCommand, timeout=2):
|
|
@@ -1364,7 +1394,7 @@ class ThreespaceSensor:
|
|
|
1364
1394
|
cmd.send_command(self.com)
|
|
1365
1395
|
self.com.close()
|
|
1366
1396
|
#TODO: Make this actually wait instead of an arbitrary sleep length
|
|
1367
|
-
time.sleep(
|
|
1397
|
+
time.sleep(self.restart_delay) #Give it time to restart
|
|
1368
1398
|
self.com.open()
|
|
1369
1399
|
self.__firmware_init()
|
|
1370
1400
|
|
|
@@ -1375,7 +1405,7 @@ class ThreespaceSensor:
|
|
|
1375
1405
|
cmd = self.commands[THREESPACE_ENTER_BOOTLOADER_COMMAND_NUM]
|
|
1376
1406
|
cmd.send_command(self.com)
|
|
1377
1407
|
#TODO: Make this actually wait instead of an arbitrary sleep length
|
|
1378
|
-
time.sleep(
|
|
1408
|
+
time.sleep(self.restart_delay) #Give it time to boot into bootloader
|
|
1379
1409
|
if self.com.reenumerates:
|
|
1380
1410
|
self.com.close()
|
|
1381
1411
|
success = self.__attempt_rediscover_self()
|
|
@@ -1439,7 +1469,7 @@ class ThreespaceSensor:
|
|
|
1439
1469
|
def bootloader_boot_firmware(self):
|
|
1440
1470
|
if not self.in_bootloader: return
|
|
1441
1471
|
self.com.write("B".encode())
|
|
1442
|
-
time.sleep(
|
|
1472
|
+
time.sleep(self.restart_delay) #Give time to boot into firmware
|
|
1443
1473
|
if self.com.reenumerates:
|
|
1444
1474
|
self.com.close()
|
|
1445
1475
|
success = self.__attempt_rediscover_self()
|
|
@@ -1456,13 +1486,14 @@ class ThreespaceSensor:
|
|
|
1456
1486
|
This may take a long time
|
|
1457
1487
|
"""
|
|
1458
1488
|
self.com.write('S'.encode())
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
response
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1489
|
+
|
|
1490
|
+
start_time = time.perf_counter()
|
|
1491
|
+
response = []
|
|
1492
|
+
while len(response) == 0 and time.perf_counter() - start_time < timeout:
|
|
1493
|
+
response = self.com.read(1)
|
|
1494
|
+
if len(response) == 0:
|
|
1495
|
+
return -1
|
|
1496
|
+
return response[0]
|
|
1466
1497
|
|
|
1467
1498
|
def bootloader_get_info(self):
|
|
1468
1499
|
self.com.write('I'.encode())
|
|
@@ -1472,14 +1503,20 @@ class ThreespaceSensor:
|
|
|
1472
1503
|
bootversion = struct.unpack(f">{_3space_format_to_external('I')}", self.com.read(2))[0]
|
|
1473
1504
|
return ThreespaceBootloaderInfo(memstart, memend, pagesize, bootversion)
|
|
1474
1505
|
|
|
1475
|
-
def bootloader_prog_mem(self, bytes: bytearray):
|
|
1506
|
+
def bootloader_prog_mem(self, bytes: bytearray, timeout=5):
|
|
1476
1507
|
memsize = len(bytes)
|
|
1477
1508
|
checksum = sum(bytes)
|
|
1478
1509
|
self.com.write('C'.encode())
|
|
1479
1510
|
self.com.write(struct.pack(f">{_3space_format_to_external('I')}", memsize))
|
|
1480
1511
|
self.com.write(bytes)
|
|
1481
1512
|
self.com.write(struct.pack(f">{_3space_format_to_external('B')}", checksum & 0xFFFF))
|
|
1482
|
-
|
|
1513
|
+
start_time = time.perf_counter()
|
|
1514
|
+
result = []
|
|
1515
|
+
while len(result) == 0 and time.perf_counter() - start_time < timeout:
|
|
1516
|
+
result = self.com.read(1)
|
|
1517
|
+
if len(result) > 0:
|
|
1518
|
+
return result[0]
|
|
1519
|
+
return -1
|
|
1483
1520
|
|
|
1484
1521
|
def bootloader_get_state(self):
|
|
1485
1522
|
self.com.write('OO'.encode()) #O is sent twice to compensate for a bug in some versions of the bootloader where the next character is ignored (except for R, do NOT send R after O, it will erase all settings)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|