yostlabs 2025.1.16__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.
@@ -0,0 +1,435 @@
1
+ from yostlabs.tss3.api import ThreespaceSensor, StreamableCommands, ThreespaceCmdResult, threespaceGetHeaderLabels
2
+
3
+ from enum import Enum
4
+ from typing import Any, Callable
5
+ from dataclasses import dataclass, field
6
+
7
+ class ThreespaceStreamingStatus(Enum):
8
+ Data = 0 #Normal data update
9
+ DataEnd = 1 #This is the last packet being sent for this data update. This allows the user to more efficently handle their callback.
10
+ #For example, if you have an expensive computation that needs done over all the data, but only once per frame, it would
11
+ #be preferable to buffer the data received via the callback and only do the computation when DataEnd is received
12
+ Paused = 2 #Streaming has been paused
13
+ Resumed = 3 #Streaming has been resumed
14
+
15
+ #Streaming manager is resetting. It is required that the callback unregisters everything is has registered
16
+ #This option is intended for shutdown purposes or in complex applications where the user needs to completely
17
+ #disable the streaming manager for some reason
18
+ Reset = 4
19
+
20
+ from typing import NamedTuple
21
+ ThreespaceStreamingOption = NamedTuple("ThreespaceStreamingOption", [("cmd", StreamableCommands), ("param", int|None)])
22
+ class ThreespaceStreamingManager:
23
+ """
24
+ A class that manages multiple clients wanting streamed data. Will update the streaming
25
+ slots and speed dynamically based on given requirements and allow those clients to
26
+ access that data without having to worry about which streaming slot is being used by what data.
27
+ """
28
+
29
+ @dataclass
30
+ class Command:
31
+ cmd: StreamableCommands = None
32
+ param: int = None
33
+
34
+ slot: int = None
35
+ registrations: set = field(default_factory=set, init=False)
36
+
37
+ active: bool = False #If not active then it must have been queued for addition, so it will be set active immediately
38
+
39
+ labels: str = None
40
+
41
+ @dataclass
42
+ class Callback:
43
+ func: Callable[[ThreespaceStreamingStatus],None] = None
44
+ hz: int = None
45
+
46
+ only_newest: bool = False
47
+
48
+ @property
49
+ def interval(self):
50
+ if self.hz is None: return None
51
+ return 1000000 // self.hz
52
+
53
+ def __init__(self, sensor: ThreespaceSensor):
54
+ self.sensor = sensor
55
+
56
+ self.num_slots = len(self.get_slots_from_sensor()) #This is just so if number of available slots ever changes, this changes to match it
57
+ self.registered_commands: dict[tuple, ThreespaceStreamingManager.Command] = {}
58
+ self.slots: list[ThreespaceStreamingManager.Command] = [] #The same as self.registered_commands, but allow indexing based on slot instead
59
+
60
+ self.last_response: ThreespaceCmdResult|None = None
61
+ self.results: dict[tuple,Any] = {}
62
+
63
+ self.callbacks: dict[Callable, ThreespaceStreamingManager.Callback] = {}
64
+
65
+ #Objects currently pausing the streaming
66
+ self.pausers: set[object] = set()
67
+ self.lockers: set[object] = set()
68
+
69
+ #Keeps track of how many packets have been read. Useful for consumers to know if the values have been updated since they last read
70
+ self.sample_count = 0
71
+
72
+ self.enabled = False
73
+ self.is_streaming = False #Store this seperately to attempt to allow using both the regular streaming and streaming manager via pausing and such
74
+
75
+ #Set the initial streaming speed
76
+ self.interval = int(self.sensor.get_settings("stream_interval"))
77
+
78
+ self.dirty = False
79
+ self.validated = False
80
+
81
+ #Control variable to manually control when updating happens here
82
+ self.block_updates = False
83
+
84
+ #Using interval instead of HZ because more precise and the result of ?stream_hz may not be exactly equal to what is set
85
+ #However the functions for interfacing with these are still done in Hz
86
+ self.max_interval = 0xFFFFFFFF
87
+ self.min_interval = 1000000 / 2000
88
+
89
+ @property
90
+ def paused(self):
91
+ return len(self.pausers) > 0
92
+
93
+ @property
94
+ def locked(self):
95
+ return len(self.lockers) > 0
96
+
97
+ def pause(self, locker: object):
98
+ if locker in self.pausers: return True
99
+ if self.locked: return False
100
+ self.pausers.add(locker)
101
+ if len(self.pausers) == 1 and self.is_streaming:
102
+ self.__stop_streaming()
103
+ for callback in self.callbacks:
104
+ callback(ThreespaceStreamingStatus.Paused)
105
+ return True
106
+
107
+ def resume(self, locker: object):
108
+ try:
109
+ self.pausers.remove(locker)
110
+ except KeyError:
111
+ return
112
+
113
+ #Attempt to start again
114
+ if len(self.pausers) == 0:
115
+ for callback in self.callbacks:
116
+ callback(ThreespaceStreamingStatus.Resumed)
117
+ self.__apply_streaming_settings_and_update_state()
118
+
119
+ def lock_modifications(self, locker: object):
120
+ """
121
+ This still allows the streaming manager to operate and register new objects. However, registration
122
+ is limited to commands and speeds that are already operatable. Essentially, after this is called,
123
+ it is not possible to do actions that require updating the sensors onboard settings/state. This gurantees
124
+ streaming will not be stopped/restarted for time sensitive applications.
125
+ Note: This INCLUDES pausing/resuming, enabling/disabling...
126
+ If you need to lock modifications, then pause or resume. The locker should unlock modifications, call the necessary function, and then lock again
127
+ """
128
+ self.lockers.add(locker)
129
+
130
+ def unlock_modifications(self, locker: object):
131
+ if not locker in self.lockers: return
132
+ self.lockers.remove(locker)
133
+ if not self.locked and self.dirty:
134
+ self.__apply_streaming_settings_and_update_state()
135
+
136
+ def reset(self):
137
+ #Prevent the callbacks unregistrations from instantly taking effect regardless of if they pass immediate_update or not
138
+ self.block_updates = True
139
+ values = list(self.callbacks.values()) #To prevent concurrent dict modification, cache this
140
+ for cb in values:
141
+ cb.func(ThreespaceStreamingStatus.Reset)
142
+ self.block_updates = False
143
+ self.lockers.clear()
144
+ self.pausers.clear()
145
+ self.__apply_streaming_settings_and_update_state()
146
+ if self.num_commands_registered != 0:
147
+ raise RuntimeError(f"Failed to reset streaming manager. {self.num_commands_registered} commands still registered.\n {self.registered_commands}")
148
+ if self.num_callbacks_registered != 0:
149
+ raise RuntimeError(f"Failed to reset streaming manager. {self.num_callbacks_registered} callbacks still registered.\n {self.callbacks}")
150
+ return True
151
+
152
+ def update(self):
153
+ if self.paused or not self.enabled or not self.sensor.is_streaming: return
154
+
155
+ self.apply_updated_settings()
156
+ self.sensor.updateStreaming()
157
+ result = self.sensor.getOldestStreamingPacket()
158
+ if result is not None:
159
+ while result is not None:
160
+ self.sample_count += 1
161
+ self.last_response = result
162
+ slot_index = 0
163
+ for data in result.data:
164
+ while not self.slots[slot_index].active: slot_index += 1
165
+ cmd = self.slots[slot_index]
166
+ info = (cmd.cmd, cmd.param)
167
+ self.results[info] = data
168
+ slot_index += 1
169
+
170
+ #Let all the callbacks know the data was updated
171
+ for cb in self.callbacks.values():
172
+ if cb.only_newest: continue
173
+ cb.func(ThreespaceStreamingStatus.Data)
174
+
175
+ result = self.sensor.getOldestStreamingPacket()
176
+
177
+ for cb in self.callbacks.values():
178
+ if cb.only_newest:
179
+ cb.func(ThreespaceStreamingStatus.Data)
180
+ cb.func(ThreespaceStreamingStatus.DataEnd)
181
+
182
+ def register_callback(self, callback: Callable[[ThreespaceStreamingStatus],None], hz=None, only_newest=False):
183
+ if callback in self.callbacks: return
184
+ self.callbacks[callback] = ThreespaceStreamingManager.Callback(callback, hz, only_newest)
185
+ self.__update_streaming_speed()
186
+
187
+ def unregister_callback(self, callback: Callable[[ThreespaceStreamingStatus],None]):
188
+ if callback not in self.callbacks: return
189
+ del self.callbacks[callback]
190
+ self.__update_streaming_speed()
191
+
192
+ def register_command(self, owner: object, command: StreamableCommands|ThreespaceStreamingOption, param=None, immediate_update=True):
193
+ """
194
+ Adds the given command to the streaming slots and starts streaming it
195
+
196
+ Parameters
197
+ ----
198
+ owner : A reference to the object registering the command. A command is only unregistered after all its owners release it
199
+ command : The command to register
200
+ param : The parameter (if any) required for the command to be streamed. The command and param together identify a single slot
201
+ immediate_update : If true, the streaming manager will immediately change the streaming slots on the sensor. If doing bulk registers, it
202
+ is useful to set this as False until the last one for performance purposes.
203
+
204
+ Returns
205
+ -------
206
+ True : Successfully registered the command
207
+ False : Failed to register the command. Streaming slots are full
208
+ """
209
+ if isinstance(command, tuple):
210
+ param = command[1]
211
+ command = command[0]
212
+ info = (command, param)
213
+ if info in self.registered_commands: #Already registered, just add this as an owner
214
+ cmd = self.registered_commands[info]
215
+ if len(cmd.registrations) == 0 and self.num_commands_registered >= self.num_slots: #No room to register
216
+ return False
217
+
218
+ cmd.registrations.add(owner)
219
+ if immediate_update and self.dirty:
220
+ return self.__apply_streaming_settings_and_update_state()
221
+ return True
222
+
223
+ if self.locked: #Wasn't already registered, so don't allow new registrations
224
+ return False
225
+
226
+ #Make sure to only consider a command registered if it has registrations
227
+ num_commands_registered = self.num_commands_registered
228
+ if num_commands_registered >= self.num_slots: #No room to register
229
+ return False
230
+
231
+ #Register the command and add it to the streaming
232
+ self.registered_commands[info] = ThreespaceStreamingManager.Command(command, param=param, slot=num_commands_registered)
233
+ self.registered_commands[info].labels = self.sensor.getStreamingLabel(command.value).data
234
+ self.registered_commands[info].registrations.add(owner)
235
+ self.dirty = True
236
+ if immediate_update:
237
+ return self.__apply_streaming_settings_and_update_state()
238
+ return True
239
+
240
+ def unregister_command(self, owner: object, command: StreamableCommands|ThreespaceStreamingOption, param=None, immediate_update=True):
241
+ """
242
+ Removes the given command to the streaming slots and starts streaming it
243
+
244
+ Parameters
245
+ ----------
246
+ owner : A reference to the object unregistering the command. A command is only unregistered after all its owners release it
247
+ command : The command to unregister
248
+ param : The param (if any) required for the command
249
+ immediate_update : If true, the streaming manager will immediately change the streaming slots on the sensor. If doing bulk unregisters, it
250
+ is useful to set this as False until the last one for performance purposes.
251
+ """
252
+ if isinstance(command, tuple):
253
+ param = command[1]
254
+ command = command[0]
255
+ info = (command, param)
256
+ if info not in self.registered_commands:
257
+ return
258
+
259
+ try:
260
+ self.registered_commands[info].registrations.remove(owner)
261
+ except KeyError:
262
+ return #This owner wasn't registered to begin with, just ignore
263
+
264
+ #Nothing else to do
265
+ if len(self.registered_commands[info].registrations) != 0:
266
+ return
267
+
268
+ #Remove the command from streaming since nothing owns it anymore
269
+ self.dirty = True
270
+ if immediate_update:
271
+ self.__apply_streaming_settings_and_update_state()
272
+
273
+
274
+ def __build_stream_slots_string(self):
275
+ cmd_strings = []
276
+ self.slots.clear()
277
+ if self.num_commands_registered == 0: return "255"
278
+ i = 0
279
+ for cmd_key in self.registered_commands:
280
+ cmd = self.registered_commands[cmd_key]
281
+ if not cmd.active: continue #Skip non active registrations
282
+ self.slots.append(cmd)
283
+ cmd.slot = i
284
+ if cmd.param == None:
285
+ cmd_strings.append(str(cmd.cmd.value))
286
+ else:
287
+ cmd_strings.append(f"{cmd.cmd.value}:{cmd.param}")
288
+ i += 1
289
+ return ','.join(cmd_strings)
290
+
291
+ #More user friendly version of __apply_streaming_settings_and_update_state that prevents the user from calling it when not needed.
292
+ def apply_updated_settings(self):
293
+ """
294
+ This applys the current settings of the streaming manager and updates its state. This is normally done automatically, however
295
+ if the user is registering/unregistering with immediate_update turned off, this can be called to force the update.
296
+ """
297
+ if not self.dirty: return self.validated
298
+ return self.__apply_streaming_settings_and_update_state()
299
+
300
+ def __apply_streaming_settings_and_update_state(self, ignore_lock=False):
301
+ """
302
+ Used to apply the current configuration this manager represents to the streaming.
303
+ This involves disabling streaming if currently running
304
+ """
305
+ if self.block_updates or (self.locked and not ignore_lock):
306
+ return False
307
+
308
+ if self.sensor.is_streaming:
309
+ self.__stop_streaming()
310
+
311
+ #Clean up any registrations that need removed and activate any that need activated
312
+ if self.dirty:
313
+ to_remove = []
314
+ for k, v in self.registered_commands.items():
315
+ if len(v.registrations) == 0:
316
+ to_remove.append(k)
317
+ continue
318
+ v.active = True
319
+ for key in to_remove:
320
+ del self.registered_commands[key]
321
+ if key in self.results:
322
+ del self.results[key]
323
+ self.dirty = False
324
+
325
+ if self.num_commands_registered > 0:
326
+ slots_string = self.__build_stream_slots_string()
327
+ err, num_successes = self.sensor.set_settings(stream_slots=slots_string, stream_interval=self.interval)
328
+ if err:
329
+ self.validated = False
330
+ return False
331
+ if not self.paused and self.enabled:
332
+ self.__start_streaming() #Re-enable
333
+
334
+ self.validated = True
335
+ return True
336
+
337
+ def __update_streaming_speed(self):
338
+ required_interval = None
339
+ for callback in self.callbacks.values():
340
+ if callback.interval is None: continue
341
+ if required_interval is None or callback.interval < required_interval:
342
+ required_interval = callback.interval
343
+
344
+ if required_interval is None: #Treat required as current to make sure the current interval is still valid
345
+ required_interval = self.interval
346
+
347
+ required_interval = min(self.max_interval, max(self.min_interval, required_interval))
348
+ if required_interval != self.interval:
349
+ print(f"Updating streaming speed from {1000000 / self.interval}hz to {1000000 / required_interval}hz")
350
+ self.interval = int(required_interval)
351
+ self.dirty = True
352
+ self.__apply_streaming_settings_and_update_state()
353
+
354
+ def __start_streaming(self):
355
+ self.sensor.startStreaming()
356
+ self.is_streaming = True
357
+
358
+ def __stop_streaming(self):
359
+ self.sensor.stopStreaming()
360
+ self.is_streaming = False
361
+
362
+ @property
363
+ def num_commands_registered(self):
364
+ return len([v for v in self.registered_commands.values() if len(v.registrations) != 0])
365
+
366
+ @property
367
+ def num_callbacks_registered(self):
368
+ return len(self.callbacks)
369
+
370
+ def get_value(self, command: StreamableCommands|ThreespaceStreamingOption, param=None):
371
+ if isinstance(command, tuple):
372
+ param = command[1]
373
+ command = command[0]
374
+ return self.results.get((command, param), None)
375
+
376
+ def get_last_response(self):
377
+ return self.last_response
378
+
379
+ def get_header(self):
380
+ return self.last_response.header
381
+
382
+ def get_cmd_labels(self):
383
+ return ','.join(cmd.labels for cmd in self.registered_commands.values())
384
+
385
+ def get_header_labels(self):
386
+ order = threespaceGetHeaderLabels(self.sensor.header_info)
387
+ return ','.join(order)
388
+
389
+ def get_response_labels(self):
390
+ return ','.join([self.get_header_labels(), self.get_cmd_labels()])
391
+
392
+ def enable(self):
393
+ if self.enabled:
394
+ return
395
+ self.enabled = True
396
+ self.__apply_streaming_settings_and_update_state()
397
+
398
+ def disable(self):
399
+ if not self.enabled:
400
+ return
401
+ if self.is_streaming:
402
+ self.__stop_streaming()
403
+ self.enabled = False
404
+
405
+ def set_max_hz(self, hz: float):
406
+ if hz <= 0 or hz > 2000:
407
+ raise ValueError(f"Invalid streaming Hz {hz}")
408
+ self.min_interval = 1000000 // hz
409
+ self.__update_streaming_speed()
410
+
411
+ def set_min_hz(self, hz: float):
412
+ if hz <= 0 or hz > 2000:
413
+ raise ValueError(f"Invalid streaming Hz {hz}")
414
+ self.max_interval = 1000000 // hz
415
+ self.__update_streaming_speed()
416
+
417
+ def get_slots_from_sensor(self):
418
+ """
419
+ get a list containing the streaming information from the current sensor
420
+ """
421
+ slot_setting: str = self.sensor.get_settings("stream_slots")
422
+ slots = slot_setting.split(',')
423
+ slot_info = []
424
+ for slot in slots:
425
+ info = slot.split(':')
426
+ slot = int(info[0]) #Ignore parameters if any
427
+ param = None
428
+ if len(info) > 1:
429
+ param = int(info[1])
430
+ if slot != 255:
431
+ slot_info.append((slot, param))
432
+ else:
433
+ slot_info.append(None)
434
+
435
+ return slot_info
@@ -0,0 +1,76 @@
1
+ from yostlabs.tss3.api import ThreespaceSensor
2
+ from xml.dom import minidom, Node
3
+
4
+ from typing import Callable
5
+
6
+ class ThreespaceFirmwareUploader:
7
+
8
+ def __init__(self, sensor: ThreespaceSensor, file_path: str = None, percentage_callback: Callable[[int],None] = None, verbose: bool = False):
9
+ self.sensor = sensor
10
+ self.set_firmware_path(file_path)
11
+ self.verbose = verbose
12
+
13
+ self.percent_complete = 0
14
+ self.callback = percentage_callback
15
+
16
+ def set_firmware_path(self, file_path: str):
17
+ if file_path is None:
18
+ self.firmware = None
19
+ return
20
+ self.firmware = minidom.parse(file_path)
21
+
22
+ def set_percent_callback(self, callback: Callable[[float],None]):
23
+ self.callback = callback
24
+
25
+ def set_verbose(self, verbose: bool):
26
+ self.verbose = verbose
27
+
28
+ def get_percent_done(self):
29
+ return self.percent_complete
30
+
31
+ def __set_percent_complete(self, percent: float):
32
+ self.percent_complete = percent
33
+ if self.callback:
34
+ self.callback(percent)
35
+
36
+ def log(self, *args):
37
+ if not self.verbose: return
38
+ print(*args)
39
+
40
+ def upload_firmware(self):
41
+ self.percent_complete = 0
42
+ if not self.sensor.in_bootloader:
43
+ self.sensor.enterBootloader()
44
+ self.__set_percent_complete(5)
45
+
46
+ boot_info = self.sensor.bootloader_get_info()
47
+
48
+ root = self.firmware.firstChild
49
+ for c in root.childNodes:
50
+ if c.nodeType == Node.ELEMENT_NODE:
51
+ name = c.nodeName
52
+ if name == "SetAddr":
53
+ self.log("Write S")
54
+ error = self.sensor.bootloader_erase_firmware()
55
+ if error:
56
+ self.log("Failed to erase firmware:", error)
57
+ else:
58
+ self.log("Successfully erased firmware")
59
+ self.__set_percent_complete(20)
60
+ elif name == "MemProgC":
61
+ mem = bytes.fromhex(c.firstChild.nodeValue)
62
+ self.log("Attempting to program", len(mem), "bytes to the chip")
63
+ cpos = 0
64
+ while cpos < len(mem):
65
+ memchunk = mem[cpos : min(len(mem), cpos + boot_info.pagesize)]
66
+ error = self.sensor.bootloader_prog_mem(memchunk)
67
+ if error:
68
+ self.log("Failed upload:", error)
69
+ else:
70
+ self.log("Wrote", len(memchunk), "bytes successfully to offset", cpos)
71
+ cpos += len(memchunk)
72
+ self.__set_percent_complete(20 + cpos / len(mem) * 79)
73
+ elif name == "Run":
74
+ self.log("Resetting with new firmware.")
75
+ self.sensor.bootloader_boot_firmware()
76
+ self.__set_percent_complete(100)
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: yostlabs
3
+ Version: 2025.1.16
4
+ Summary: Python resources and API for 3Space sensors from Yost Labs Inc.
5
+ Project-URL: Homepage, https://yostlabs.com/
6
+ Project-URL: Repository, https://github.com/YostLabs/3SpacePythonPackage/tree/main
7
+ Project-URL: Issues, https://github.com/YostLabs/3SpacePythonPackage/issues
8
+ Author-email: "Yost Labs Inc." <techsupport@yostlabs.com>, Andy Riedlinger <techsupport@yostlabs.com>
9
+ License-File: LICENSE
10
+ Keywords: 3space,threespace,yost
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: numpy
17
+ Requires-Dist: pyserial
18
+ Description-Content-Type: text/markdown
19
+
20
+ <center><h4>API and Resources for Yost Labs 3.0 Threespace sensors.</h4></center>
21
+
22
+ ## Installation
23
+
24
+ `python -m pip install yostlabs`
25
+
26
+ ## Basic Usage
27
+
28
+ ```Python
29
+ from yostlabs.tss3.api import ThreespaceSensor
30
+
31
+ #Will auto detect a 3-Space sensor connected to the machine via a USB connection
32
+ sensor = ThreespaceSensor()
33
+
34
+ result = sensor.getPrimaryCorrectedAccelVec()
35
+ print(result)
36
+
37
+ sensor.cleanup()
38
+ ```
39
+
40
+ Click [here](https://github.com/YostLabs/3SpacePythonPackage/tree/main/Examples) for more examples.
41
+
42
+ ## Communication
43
+
44
+ The ThreespaceSensor class utilizes a ThreespaceComClass to define the hardware communication interface between the device utlizing this API and the Threespace Sensor. Currently only the ThreespaceSerialComClass is available for use with the API. New ComClasses for different interfaces will be added to the [communication package](https://github.com/YostLabs/3SpacePythonPackage/tree/main/src/yostlabs/communication) in the future.
45
+
46
+ To create your own ThreespaceComClass, take a look at the necessary interface definitions [here](https://github.com/YostLabs/3SpacePythonPackage/blob/main/src/yostlabs/communication/base.py) and the Serial implementation [here](https://github.com/YostLabs/3SpacePythonPackage/blob/main/src/yostlabs/communication/serial.py).
47
+
48
+ ## Documentation
49
+
50
+ WIP. Please review the example scripts. For further assistance contact techsupport@yostlabs.com.
@@ -0,0 +1,20 @@
1
+ yostlabs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ yostlabs/communication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ yostlabs/communication/base.py,sha256=i82t4Kq3B8CpPqGNpGc737rXFul4dTkutXt5N5OYuQg,2807
4
+ yostlabs/communication/serial.py,sha256=cOv3CxloQo7sCjdAEUzkfZuZieqy-JbsW2Zwavc9LT8,5514
5
+ yostlabs/math/__init__.py,sha256=JFzsPQ4AbsX1AH1brBpn1c_Pa_ItF43__D3mlPvA2a4,34
6
+ yostlabs/math/quaternion.py,sha256=YyvbSrTPXGS8BsQJCn2tjdzIZ9WeDzfUe7dIDKeWsAM,4989
7
+ yostlabs/math/vector.py,sha256=CPtIxJXelCidGxTBrz6vZwLv-qXlccpAlYODaKJnWNw,991
8
+ yostlabs/tss3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ yostlabs/tss3/api.py,sha256=75TQ13UzP1_u14NwcoJLhWsITXfGPYhzZM4HF05fEPU,74084
10
+ yostlabs/tss3/consts.py,sha256=YzCMHlsYc193H9Qml0Q0f6i68boZJM55m3loWYwr7LA,903
11
+ yostlabs/tss3/eepts.py,sha256=7A7sCyOfDiJgw5Y9pGneg-5YgNvcfKtqeS9FoVWfJO8,9540
12
+ yostlabs/tss3/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ yostlabs/tss3/utils/calibration.py,sha256=42jCEzfTXoHuPZ4e-30N1ijOhkz9ld4PQnhX6AhTrZE,7069
14
+ yostlabs/tss3/utils/parser.py,sha256=thM5s70CZvehM5qP3AGVgHs6woeQM-wmA7hIcRO3MlY,11332
15
+ yostlabs/tss3/utils/streaming.py,sha256=218U29LmsLel42kd6g63Hi9XnovRqFVMofO0GEOAAA0,18990
16
+ yostlabs/tss3/utils/version.py,sha256=NT2H9l-oIRCYhV_yjf5UjkadoJQ0IN4eLl8y__pyTPc,3001
17
+ yostlabs-2025.1.16.dist-info/METADATA,sha256=sF23w23gw_zH3Iw_P15waWE0ZQu8mHrA8eRgbmAfsfs,2184
18
+ yostlabs-2025.1.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ yostlabs-2025.1.16.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
20
+ yostlabs-2025.1.16.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Yost Labs Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.