yostlabs 2025.4.28__py3-none-any.whl → 2025.5.14__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.
@@ -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
- self.dirty = False
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
- return self.__apply_streaming_settings_and_update_state()
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.dirty = True
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.dirty = True
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.dirty = True
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.dirty:
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.dirty = False
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.interval)
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.interval
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.interval:
367
- print(f"Updating streaming speed from {1000000 / self.interval}hz to {1000000 / required_interval}hz")
368
- self.interval = int(required_interval)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yostlabs
3
- Version: 2025.4.28
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
@@ -1,7 +1,7 @@
1
1
  yostlabs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  yostlabs/communication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  yostlabs/communication/base.py,sha256=ahAIQndfo9ifX6Lf2NeEaHpIhRJ_uBv6jv9P7N3Rbhg,2884
4
- yostlabs/communication/ble.py,sha256=lZcUQah9QjFxBwhrzUAGSWLzok0NVVweFHp_bN15hvs,15511
4
+ yostlabs/communication/ble.py,sha256=4_DUSx-tR0fMeOjS09D5jdGpPC1LlZGqBa96vq0mZQ8,15797
5
5
  yostlabs/communication/serial.py,sha256=j7SksPhd2mCvcMIGVvPcAAhYOE29K6uGLwZCwD-b21E,5685
6
6
  yostlabs/math/__init__.py,sha256=JFzsPQ4AbsX1AH1brBpn1c_Pa_ItF43__D3mlPvA2a4,34
7
7
  yostlabs/math/quaternion.py,sha256=vQQmT5T0FXAOrZ27cj007Gb_sfEhs7h0Sz6nYxNc5hQ,6357
@@ -12,10 +12,10 @@ yostlabs/tss3/consts.py,sha256=RwhqmKIXRGpRdusss3q17ukCuRS96ZsvBl6y0mjF0b4,2404
12
12
  yostlabs/tss3/eepts.py,sha256=7A7sCyOfDiJgw5Y9pGneg-5YgNvcfKtqeS9FoVWfJO8,9540
13
13
  yostlabs/tss3/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  yostlabs/tss3/utils/calibration.py,sha256=42jCEzfTXoHuPZ4e-30N1ijOhkz9ld4PQnhX6AhTrZE,7069
15
- yostlabs/tss3/utils/parser.py,sha256=thM5s70CZvehM5qP3AGVgHs6woeQM-wmA7hIcRO3MlY,11332
16
- yostlabs/tss3/utils/streaming.py,sha256=My6SSvQSbrxKC2-mZd0tQK9vGtmX8NpdQsRRShwjUs8,20024
15
+ yostlabs/tss3/utils/parser.py,sha256=QfjjFeeIcnWjVbEjSx2yqOsNuoTK3DjkfT6BOMcQOsg,11346
16
+ yostlabs/tss3/utils/streaming.py,sha256=G2OjSIL9zub0EbkgDGDWaqSXoRY6MJzMD4mazWOCUOA,22419
17
17
  yostlabs/tss3/utils/version.py,sha256=NT2H9l-oIRCYhV_yjf5UjkadoJQ0IN4eLl8y__pyTPc,3001
18
- yostlabs-2025.4.28.dist-info/METADATA,sha256=g70cstHnHfM97YbkHVgsedm0OdSsCt3g6SWqCSkA850,2722
19
- yostlabs-2025.4.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- yostlabs-2025.4.28.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
21
- yostlabs-2025.4.28.dist-info/RECORD,,
18
+ yostlabs-2025.5.14.dist-info/METADATA,sha256=T7bWKnk56TDkGsErHCMnn2nvpd3bmRcFm2mNcajRXC4,2722
19
+ yostlabs-2025.5.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ yostlabs-2025.5.14.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
21
+ yostlabs-2025.5.14.dist-info/RECORD,,