yostlabs 2025.4.28__tar.gz → 2025.5.14__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.4.28 → yostlabs-2025.5.14}/PKG-INFO +1 -1
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/pyproject.toml +1 -1
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/communication/ble.py +10 -1
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/utils/parser.py +1 -2
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/utils/streaming.py +82 -27
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/.gitignore +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/embedded_2024_dec_20.xml +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_ble.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_commands.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_component_specific_settings_and_commands.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_firmware_upload.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_parsing_stored_binary.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_read_settings.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_streaming.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_streaming_manager.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/Examples/example_write_settings.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/LICENSE +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/README.md +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/__init__.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/communication/__init__.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/communication/base.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/communication/serial.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/math/__init__.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/math/quaternion.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/math/vector.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/__init__.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/api.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/consts.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/eepts.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/utils/__init__.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/src/yostlabs/tss3/utils/calibration.py +0 -0
- {yostlabs-2025.4.28 → yostlabs-2025.5.14}/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.5.14
|
|
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.05.14"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Yost Labs Inc.", email="techsupport@yostlabs.com" },
|
|
11
11
|
{ name="Andy Riedlinger", email="techsupport@yostlabs.com" },
|
|
@@ -236,8 +236,17 @@ class ThreespaceBLEComClass(ThreespaceComClass):
|
|
|
236
236
|
return False
|
|
237
237
|
|
|
238
238
|
@property
|
|
239
|
-
def name(self) -> str:
|
|
239
|
+
def name(self) -> str | None:
|
|
240
|
+
"""
|
|
241
|
+
The name of the device. This may be the Address or the Local Name of the device
|
|
242
|
+
depending on how discovery was done.
|
|
243
|
+
May also be None
|
|
244
|
+
"""
|
|
240
245
|
return self.__name
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def address(self) -> str:
|
|
249
|
+
return self.client.address
|
|
241
250
|
|
|
242
251
|
SCANNER = None
|
|
243
252
|
SCANNER_EVENT_LOOP = None
|
|
@@ -137,7 +137,7 @@ class ThreespaceBinaryParser:
|
|
|
137
137
|
if "read_size" not in kwargs:
|
|
138
138
|
raise ValueError("Missing arguement 'read_size' when registering the fileReadBytes command with the binary parser")
|
|
139
139
|
raise NotImplementedError("The fileReadBytes command has yet to be implemented for the ThreespaceBinaryParser")
|
|
140
|
-
elif cmd.info.num == THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM:
|
|
140
|
+
elif cmd.info.num == THREESPACE_GET_STREAMING_BATCH_COMMAND_NUM and not isinstance(cmd, ThreespaceGetStreamingBatchCommand):
|
|
141
141
|
if "stream_slots" not in kwargs:
|
|
142
142
|
raise ValueError("Missing arguement 'stream_slots' when registering the getStreamingBatch command with the binary parser")
|
|
143
143
|
cmd = ThreespaceGetStreamingBatchCommand(kwargs['stream_slots'])
|
|
@@ -221,7 +221,6 @@ class ThreespaceBinaryParser:
|
|
|
221
221
|
return None
|
|
222
222
|
|
|
223
223
|
if self.header_info.checksum_enabled and not math.isnan(self.__parsing_msg_length): #Can validate checksum before parsing
|
|
224
|
-
print("Pre validating checksum")
|
|
225
224
|
if not self.__peek_checksum():
|
|
226
225
|
#Data corruption/Misalignment error
|
|
227
226
|
if self.verbose and not self.misaligned:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from yostlabs.tss3.api import ThreespaceSensor, StreamableCommands, ThreespaceCmdResult, threespaceGetHeaderLabels
|
|
1
|
+
from yostlabs.tss3.api import ThreespaceSensor, StreamableCommands, ThreespaceCmdResult, threespaceGetHeaderLabels, \
|
|
2
|
+
ThreespaceGetStreamingBatchCommand, threespaceCommandGet
|
|
2
3
|
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any, Callable
|
|
@@ -17,6 +18,17 @@ class ThreespaceStreamingStatus(Enum):
|
|
|
17
18
|
#disable the streaming manager for some reason
|
|
18
19
|
Reset = 4
|
|
19
20
|
|
|
21
|
+
#Used for when the callback signature changed to allow user data, but maintain backwards compatability
|
|
22
|
+
def _api_compatible_callback(func: Callable):
|
|
23
|
+
arg_count = func.__code__.co_argcount
|
|
24
|
+
if hasattr(func, "__self__"): arg_count -= 1 #Do NOT count self
|
|
25
|
+
|
|
26
|
+
if arg_count == 1:
|
|
27
|
+
def without_userdata(status: ThreespaceStreamingStatus, user_data: Any):
|
|
28
|
+
return func(status)
|
|
29
|
+
return without_userdata
|
|
30
|
+
return func
|
|
31
|
+
|
|
20
32
|
from typing import NamedTuple
|
|
21
33
|
ThreespaceStreamingOption = NamedTuple("ThreespaceStreamingOption", [("cmd", StreamableCommands), ("param", int|None)])
|
|
22
34
|
class ThreespaceStreamingManager:
|
|
@@ -44,6 +56,7 @@ class ThreespaceStreamingManager:
|
|
|
44
56
|
hz: int = None
|
|
45
57
|
|
|
46
58
|
only_newest: bool = False
|
|
59
|
+
user_data: Any = None
|
|
47
60
|
|
|
48
61
|
@property
|
|
49
62
|
def interval(self):
|
|
@@ -73,9 +86,12 @@ class ThreespaceStreamingManager:
|
|
|
73
86
|
self.is_streaming = False #Store this seperately to attempt to allow using both the regular streaming and streaming manager via pausing and such
|
|
74
87
|
|
|
75
88
|
#Set the initial streaming speed
|
|
76
|
-
self.interval = int(self.sensor.get_settings("stream_interval"))
|
|
89
|
+
self.interval = int(self.sensor.get_settings("stream_interval"))
|
|
90
|
+
self.desired_interval = self.interval
|
|
77
91
|
|
|
78
|
-
|
|
92
|
+
#Registrations and update rate dirt are handled separately for some situations.
|
|
93
|
+
#Primarily, we want registration to succeed even if the update can't update the rate.
|
|
94
|
+
self.dirty_registrations = False
|
|
79
95
|
self.validated = False
|
|
80
96
|
|
|
81
97
|
#Control variable to manually control when updating happens here
|
|
@@ -86,6 +102,14 @@ class ThreespaceStreamingManager:
|
|
|
86
102
|
self.max_interval = 0xFFFFFFFF
|
|
87
103
|
self.min_interval = 1000000 / 2000
|
|
88
104
|
|
|
105
|
+
@property
|
|
106
|
+
def dirty(self):
|
|
107
|
+
return self.dirty_registrations or self.dirty_rate
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def dirty_rate(self):
|
|
111
|
+
return self.desired_interval != self.interval
|
|
112
|
+
|
|
89
113
|
@property
|
|
90
114
|
def paused(self):
|
|
91
115
|
return len(self.pausers) > 0
|
|
@@ -100,8 +124,8 @@ class ThreespaceStreamingManager:
|
|
|
100
124
|
self.pausers.add(locker)
|
|
101
125
|
if len(self.pausers) == 1 and self.is_streaming:
|
|
102
126
|
self.__stop_streaming()
|
|
103
|
-
for callback in self.callbacks:
|
|
104
|
-
callback(ThreespaceStreamingStatus.Paused)
|
|
127
|
+
for callback in self.callbacks.values():
|
|
128
|
+
callback.func(ThreespaceStreamingStatus.Paused, callback.user_data)
|
|
105
129
|
return True
|
|
106
130
|
|
|
107
131
|
def resume(self, locker: object):
|
|
@@ -112,8 +136,8 @@ class ThreespaceStreamingManager:
|
|
|
112
136
|
|
|
113
137
|
#Attempt to start again
|
|
114
138
|
if len(self.pausers) == 0:
|
|
115
|
-
for callback in self.callbacks:
|
|
116
|
-
callback(ThreespaceStreamingStatus.Resumed)
|
|
139
|
+
for callback in self.callbacks.values():
|
|
140
|
+
callback.func(ThreespaceStreamingStatus.Resumed, callback.user_data)
|
|
117
141
|
self.__apply_streaming_settings_and_update_state()
|
|
118
142
|
|
|
119
143
|
def lock_modifications(self, locker: object):
|
|
@@ -138,7 +162,7 @@ class ThreespaceStreamingManager:
|
|
|
138
162
|
self.block_updates = True
|
|
139
163
|
values = list(self.callbacks.values()) #To prevent concurrent dict modification, cache this
|
|
140
164
|
for cb in values:
|
|
141
|
-
cb.func(ThreespaceStreamingStatus.Reset)
|
|
165
|
+
cb.func(ThreespaceStreamingStatus.Reset, cb.user_data)
|
|
142
166
|
self.block_updates = False
|
|
143
167
|
self.lockers.clear()
|
|
144
168
|
self.pausers.clear()
|
|
@@ -170,18 +194,18 @@ class ThreespaceStreamingManager:
|
|
|
170
194
|
#Let all the callbacks know the data was updated
|
|
171
195
|
for cb in self.callbacks.values():
|
|
172
196
|
if cb.only_newest: continue
|
|
173
|
-
cb.func(ThreespaceStreamingStatus.Data)
|
|
197
|
+
cb.func(ThreespaceStreamingStatus.Data, cb.user_data)
|
|
174
198
|
|
|
175
199
|
result = self.sensor.getOldestStreamingPacket()
|
|
176
200
|
|
|
177
201
|
for cb in self.callbacks.values():
|
|
178
202
|
if cb.only_newest:
|
|
179
|
-
cb.func(ThreespaceStreamingStatus.Data)
|
|
180
|
-
cb.func(ThreespaceStreamingStatus.DataEnd)
|
|
203
|
+
cb.func(ThreespaceStreamingStatus.Data, cb.user_data)
|
|
204
|
+
cb.func(ThreespaceStreamingStatus.DataEnd, cb.user_data)
|
|
181
205
|
|
|
182
|
-
def register_callback(self, callback: Callable[[ThreespaceStreamingStatus],None], hz=None, only_newest=False):
|
|
206
|
+
def register_callback(self, callback: Callable[[ThreespaceStreamingStatus,Any],None], hz=None, only_newest=False, user_data=None):
|
|
183
207
|
if callback in self.callbacks: return
|
|
184
|
-
self.callbacks[callback] = ThreespaceStreamingManager.Callback(callback, hz, only_newest)
|
|
208
|
+
self.callbacks[callback] = ThreespaceStreamingManager.Callback(_api_compatible_callback(callback), hz, only_newest, user_data=user_data)
|
|
185
209
|
self.__update_streaming_speed()
|
|
186
210
|
|
|
187
211
|
def unregister_callback(self, callback: Callable[[ThreespaceStreamingStatus],None]):
|
|
@@ -217,7 +241,11 @@ class ThreespaceStreamingManager:
|
|
|
217
241
|
|
|
218
242
|
cmd.registrations.add(owner)
|
|
219
243
|
if immediate_update and self.dirty:
|
|
220
|
-
|
|
244
|
+
updated = self.__apply_streaming_settings_and_update_state()
|
|
245
|
+
if self.dirty_registrations:
|
|
246
|
+
return updated
|
|
247
|
+
else: #The rate was dirty, that does not affect the registration process
|
|
248
|
+
return True
|
|
221
249
|
return True
|
|
222
250
|
|
|
223
251
|
if self.locked: #Wasn't already registered, so don't allow new registrations
|
|
@@ -232,7 +260,7 @@ class ThreespaceStreamingManager:
|
|
|
232
260
|
self.registered_commands[info] = ThreespaceStreamingManager.Command(command, param=param, slot=num_commands_registered)
|
|
233
261
|
self.registered_commands[info].labels = self.sensor.getStreamingLabel(command.value).data
|
|
234
262
|
self.registered_commands[info].registrations.add(owner)
|
|
235
|
-
self.
|
|
263
|
+
self.dirty_registrations = True
|
|
236
264
|
if immediate_update:
|
|
237
265
|
return self.__apply_streaming_settings_and_update_state()
|
|
238
266
|
return True
|
|
@@ -266,7 +294,7 @@ class ThreespaceStreamingManager:
|
|
|
266
294
|
return
|
|
267
295
|
|
|
268
296
|
#Remove the command from streaming since nothing owns it anymore
|
|
269
|
-
self.
|
|
297
|
+
self.dirty_registrations = True
|
|
270
298
|
if immediate_update:
|
|
271
299
|
self.__apply_streaming_settings_and_update_state()
|
|
272
300
|
|
|
@@ -284,7 +312,7 @@ class ThreespaceStreamingManager:
|
|
|
284
312
|
if owner in registered_command.registrations:
|
|
285
313
|
registered_command.registrations.remove(owner)
|
|
286
314
|
if len(registered_command.registrations) == 0:
|
|
287
|
-
self.
|
|
315
|
+
self.dirty_registrations = True
|
|
288
316
|
|
|
289
317
|
if self.dirty and immediate_update:
|
|
290
318
|
self.__apply_streaming_settings_and_update_state()
|
|
@@ -327,7 +355,7 @@ class ThreespaceStreamingManager:
|
|
|
327
355
|
self.__stop_streaming()
|
|
328
356
|
|
|
329
357
|
#Clean up any registrations that need removed and activate any that need activated
|
|
330
|
-
if self.
|
|
358
|
+
if self.dirty_registrations:
|
|
331
359
|
to_remove = []
|
|
332
360
|
for k, v in self.registered_commands.items():
|
|
333
361
|
if len(v.registrations) == 0:
|
|
@@ -338,21 +366,24 @@ class ThreespaceStreamingManager:
|
|
|
338
366
|
del self.registered_commands[key]
|
|
339
367
|
if key in self.results:
|
|
340
368
|
del self.results[key]
|
|
341
|
-
self.
|
|
369
|
+
self.dirty_registrations = False
|
|
342
370
|
|
|
343
371
|
if self.num_commands_registered > 0:
|
|
344
372
|
slots_string = self.__build_stream_slots_string()
|
|
345
|
-
err, num_successes = self.sensor.set_settings(stream_slots=slots_string, stream_interval=self.
|
|
373
|
+
err, num_successes = self.sensor.set_settings(stream_slots=slots_string, stream_interval=self.desired_interval)
|
|
346
374
|
if err:
|
|
347
375
|
self.validated = False
|
|
348
376
|
return False
|
|
377
|
+
self.interval = self.desired_interval
|
|
349
378
|
if not self.paused and self.enabled:
|
|
350
379
|
self.__start_streaming() #Re-enable
|
|
351
380
|
|
|
352
381
|
self.validated = True
|
|
353
382
|
return True
|
|
354
383
|
|
|
355
|
-
def __update_streaming_speed(self):
|
|
384
|
+
def __update_streaming_speed(self):
|
|
385
|
+
if self.locked: return #Don't update the desired speed if modifications are locked
|
|
386
|
+
|
|
356
387
|
required_interval = None
|
|
357
388
|
for callback in self.callbacks.values():
|
|
358
389
|
if callback.interval is None: continue
|
|
@@ -360,13 +391,12 @@ class ThreespaceStreamingManager:
|
|
|
360
391
|
required_interval = callback.interval
|
|
361
392
|
|
|
362
393
|
if required_interval is None: #Treat required as current to make sure the current interval is still valid
|
|
363
|
-
required_interval = self.
|
|
394
|
+
required_interval = self.desired_interval
|
|
364
395
|
|
|
365
396
|
required_interval = min(self.max_interval, max(self.min_interval, required_interval))
|
|
366
|
-
if required_interval != self.
|
|
367
|
-
print(f"Updating
|
|
368
|
-
self.
|
|
369
|
-
self.dirty = True
|
|
397
|
+
if required_interval != self.desired_interval:
|
|
398
|
+
print(f"Updating desired speed from {1000000 / self.desired_interval}hz to {1000000 / required_interval}hz")
|
|
399
|
+
self.desired_interval = int(required_interval)
|
|
370
400
|
self.__apply_streaming_settings_and_update_state()
|
|
371
401
|
|
|
372
402
|
def __start_streaming(self):
|
|
@@ -450,4 +480,29 @@ class ThreespaceStreamingManager:
|
|
|
450
480
|
else:
|
|
451
481
|
slot_info.append(None)
|
|
452
482
|
|
|
453
|
-
return slot_info
|
|
483
|
+
return slot_info
|
|
484
|
+
|
|
485
|
+
#Utility functions
|
|
486
|
+
def get_stream_options_from_str(string: str):
|
|
487
|
+
options = []
|
|
488
|
+
slots = string.split(',')
|
|
489
|
+
for slot in slots:
|
|
490
|
+
slot = slot.split(':')
|
|
491
|
+
cmd = int(slot[0])
|
|
492
|
+
if cmd == 255: continue #Ignore 255
|
|
493
|
+
|
|
494
|
+
#Get the param if any
|
|
495
|
+
param = None
|
|
496
|
+
if len(slot) > 2:
|
|
497
|
+
raise Exception()
|
|
498
|
+
if len(slot) == 2:
|
|
499
|
+
param = int(slot[1])
|
|
500
|
+
|
|
501
|
+
stream_option = ThreespaceStreamingOption(StreamableCommands(cmd), param)
|
|
502
|
+
options.append(stream_option)
|
|
503
|
+
|
|
504
|
+
return options
|
|
505
|
+
|
|
506
|
+
def stream_options_to_command(options: list[ThreespaceStreamingOption]):
|
|
507
|
+
commands = [threespaceCommandGet(v.cmd.value) for v in options]
|
|
508
|
+
return ThreespaceGetStreamingBatchCommand(commands)
|
|
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
|
|
File without changes
|