yostlabs 2025.10.29.1__py3-none-any.whl → 2025.10.29.2__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/bluetooth.py +73 -9
- {yostlabs-2025.10.29.1.dist-info → yostlabs-2025.10.29.2.dist-info}/METADATA +1 -1
- {yostlabs-2025.10.29.1.dist-info → yostlabs-2025.10.29.2.dist-info}/RECORD +5 -5
- {yostlabs-2025.10.29.1.dist-info → yostlabs-2025.10.29.2.dist-info}/WHEEL +0 -0
- {yostlabs-2025.10.29.1.dist-info → yostlabs-2025.10.29.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,8 @@ from yostlabs.communication.socket import ThreespaceSocketComClass
|
|
|
2
2
|
import bluetooth
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
|
+
import sys
|
|
6
|
+
import math
|
|
5
7
|
|
|
6
8
|
from dataclasses import dataclass
|
|
7
9
|
from typing import Callable, Generator
|
|
@@ -94,17 +96,21 @@ class ScannerResult:
|
|
|
94
96
|
|
|
95
97
|
class Scanner:
|
|
96
98
|
|
|
97
|
-
def __init__(self,
|
|
99
|
+
def __init__(self, desired_scan_time=5):
|
|
98
100
|
self.enabled = False
|
|
99
101
|
self.done = True
|
|
100
102
|
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
103
|
+
self.continous = False #If true, will constantly update the nearby devices
|
|
104
|
+
self.execute = False #Set to true to trigger a read when not in continous mode
|
|
105
|
+
self.nearby = None #The list of nearby devices
|
|
106
|
+
self.thread = None #The thread running the asynchronous scanner
|
|
107
|
+
self.updated = False #Set to true when self.nearby has been updated
|
|
108
|
+
self.duration = math.ceil(desired_scan_time / 1.28) * 1.28 #The time, in second, each scan lasts. Scanning is in 1.28 second intervals, so this may differ from the desired scan time
|
|
105
109
|
|
|
106
110
|
def start(self):
|
|
107
|
-
if not self.done:
|
|
111
|
+
if not self.done:
|
|
112
|
+
self.execute = True
|
|
113
|
+
return
|
|
108
114
|
self.thread = threading.Thread(target=self.process, daemon=True)
|
|
109
115
|
self.enabled = True
|
|
110
116
|
self.done = False
|
|
@@ -114,6 +120,9 @@ class Scanner:
|
|
|
114
120
|
def stop(self):
|
|
115
121
|
self.enabled = False
|
|
116
122
|
|
|
123
|
+
def set_continous(self, continous: bool):
|
|
124
|
+
self.continous = continous
|
|
125
|
+
|
|
117
126
|
def get_most_recent(self):
|
|
118
127
|
if not self.updated: return None
|
|
119
128
|
self.updated = False
|
|
@@ -125,20 +134,57 @@ class Scanner:
|
|
|
125
134
|
|
|
126
135
|
def process(self):
|
|
127
136
|
while self.enabled:
|
|
128
|
-
|
|
137
|
+
if not self.continous and not self.execute: continue
|
|
138
|
+
nearby = bluetooth.discover_devices(duration=math.ceil(self.duration/1.28), lookup_names=True, lookup_class=True)
|
|
129
139
|
self.nearby = [ScannerResult(addr, name, decode_class_of_device(cod)) for addr, name, cod in nearby]
|
|
140
|
+
self.execute = False
|
|
130
141
|
self.updated = True
|
|
131
142
|
self.done = True
|
|
132
143
|
|
|
144
|
+
if sys.platform == "win32":
|
|
145
|
+
import ctypes
|
|
146
|
+
from ctypes import wintypes
|
|
147
|
+
|
|
148
|
+
# Define BLUETOOTH_ADDRESS structure
|
|
149
|
+
class BLUETOOTH_ADDRESS(ctypes.Structure):
|
|
150
|
+
_fields_ = [("rgBytes", ctypes.c_byte * 6)]
|
|
151
|
+
|
|
152
|
+
# Load Bluetooth API
|
|
153
|
+
win_bluetooth = ctypes.WinDLL("BluetoothAPIs.dll")
|
|
154
|
+
|
|
155
|
+
# Define function prototype
|
|
156
|
+
BluetoothRemoveDevice = win_bluetooth.BluetoothRemoveDevice
|
|
157
|
+
BluetoothRemoveDevice.argtypes = [ctypes.POINTER(BLUETOOTH_ADDRESS)]
|
|
158
|
+
BluetoothRemoveDevice.restype = wintypes.BOOL
|
|
159
|
+
|
|
160
|
+
def remove_device(mac_address: str):
|
|
161
|
+
# Convert MAC string "XX:XX:XX:XX:XX:XX" -> bytes reversed
|
|
162
|
+
addr_bytes = bytes.fromhex(mac_address.replace(":", ""))[::-1]
|
|
163
|
+
addr = BLUETOOTH_ADDRESS()
|
|
164
|
+
ctypes.memmove(addr.rgBytes, addr_bytes, 6)
|
|
165
|
+
|
|
166
|
+
err = BluetoothRemoveDevice(ctypes.byref(addr))
|
|
167
|
+
print("Remove device status:", err)
|
|
168
|
+
if not err:
|
|
169
|
+
print(f"Successfully removed device {mac_address}")
|
|
170
|
+
else:
|
|
171
|
+
if err == 1168: # ERROR_NOT_FOUND
|
|
172
|
+
print(f"Device {mac_address} not found or already unpaired.")
|
|
173
|
+
else:
|
|
174
|
+
print(f"Failed to remove device {mac_address}. Error code: {err}")
|
|
175
|
+
else:
|
|
176
|
+
def remove_device(mac_address: str):
|
|
177
|
+
raise NotImplementedError("Only implemented on windows")
|
|
178
|
+
|
|
133
179
|
class ThreespaceBluetoothComClass(ThreespaceSocketComClass):
|
|
134
180
|
|
|
135
|
-
SCANNER = None
|
|
181
|
+
SCANNER: Scanner = None
|
|
136
182
|
|
|
137
183
|
def __init__(self, addr: str, name: str = None, connection_timeout=None):
|
|
138
184
|
super().__init__(bluetooth.BluetoothSocket(bluetooth.Protocols.RFCOMM), (addr, 1), connection_timeout=connection_timeout)
|
|
139
185
|
self.address = addr
|
|
140
186
|
self.__name = name or addr
|
|
141
|
-
|
|
187
|
+
|
|
142
188
|
@property
|
|
143
189
|
def name(self) -> str:
|
|
144
190
|
return self.__name
|
|
@@ -149,6 +195,11 @@ class ThreespaceBluetoothComClass(ThreespaceSocketComClass):
|
|
|
149
195
|
cls.SCANNER = Scanner()
|
|
150
196
|
cls.SCANNER.start()
|
|
151
197
|
|
|
198
|
+
@classmethod
|
|
199
|
+
def set_scanner_continous(cls, continous: bool):
|
|
200
|
+
cls.__lazy_init_scanner()
|
|
201
|
+
cls.SCANNER.set_continous(continous)
|
|
202
|
+
|
|
152
203
|
@staticmethod
|
|
153
204
|
def __default_filter(result: ScannerResult):
|
|
154
205
|
return result.class_of_device.major_class == "Wearable" and result.class_of_device.minor_class == "Minor code 0"
|
|
@@ -162,6 +213,7 @@ class ThreespaceBluetoothComClass(ThreespaceSocketComClass):
|
|
|
162
213
|
cls.__lazy_init_scanner()
|
|
163
214
|
if filter is None:
|
|
164
215
|
filter = cls.__default_filter
|
|
216
|
+
cls.SCANNER.start()
|
|
165
217
|
if wait_for_update:
|
|
166
218
|
cls.SCANNER.updated = False
|
|
167
219
|
while not cls.SCANNER.updated: time.sleep(0.1)
|
|
@@ -171,6 +223,18 @@ class ThreespaceBluetoothComClass(ThreespaceSocketComClass):
|
|
|
171
223
|
name = device_info.name or None
|
|
172
224
|
yield ThreespaceBluetoothComClass(device_info.address, name)
|
|
173
225
|
|
|
226
|
+
@staticmethod
|
|
227
|
+
def unpair(address: str):
|
|
228
|
+
"""
|
|
229
|
+
It is recommended to call this after done with a ThreespaceBluetoothComClass object
|
|
230
|
+
so that windows will not report the device as still being available when powered off.
|
|
231
|
+
This occurs because windows Bluetooth detect functions report nearby devices & paired devices,
|
|
232
|
+
regardless of their power status. By removing the device from the list of paired devices, auto detect
|
|
233
|
+
will only report actual nearby and powered devices.
|
|
234
|
+
EX: ThreespaceBluetoothComClass.unpair(com.address)
|
|
235
|
+
"""
|
|
236
|
+
remove_device(address)
|
|
237
|
+
|
|
174
238
|
|
|
175
239
|
if __name__ == "__main__":
|
|
176
240
|
from yostlabs.tss3.api import ThreespaceSensor
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yostlabs
|
|
3
|
-
Version: 2025.10.29.
|
|
3
|
+
Version: 2025.10.29.2
|
|
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
|
|
@@ -2,7 +2,7 @@ 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
4
|
yostlabs/communication/ble.py,sha256=3hMnIbmKU0srpFmZyhzkXjHlRb3yX1D9aOO1uKKAhFA,19794
|
|
5
|
-
yostlabs/communication/bluetooth.py,sha256=
|
|
5
|
+
yostlabs/communication/bluetooth.py,sha256=GuqCRauGxOt0smh9qLA1NCfs7s5onZJjngXZsVuq5pc,9212
|
|
6
6
|
yostlabs/communication/serial.py,sha256=Ex41l3ePXWrZDF8ZGxWoQs1tR0yfwZaSdG7a597zBsA,6505
|
|
7
7
|
yostlabs/communication/socket.py,sha256=mZw_9-5teIPUMxLXQh7kVLu6CJxCNFC7a9CnhSAI2Og,3822
|
|
8
8
|
yostlabs/math/__init__.py,sha256=JFzsPQ4AbsX1AH1brBpn1c_Pa_ItF43__D3mlPvA2a4,34
|
|
@@ -17,7 +17,7 @@ yostlabs/tss3/utils/calibration.py,sha256=J7RnY23MUUo4lmfapHavUqrTArFo4PxGKw68sL
|
|
|
17
17
|
yostlabs/tss3/utils/parser.py,sha256=ij_kQpB3EukOq3O8wQTYhkqBP-OneiHMUcsHLuL6m-8,11347
|
|
18
18
|
yostlabs/tss3/utils/streaming.py,sha256=G2OjSIL9zub0EbkgDGDWaqSXoRY6MJzMD4mazWOCUOA,22419
|
|
19
19
|
yostlabs/tss3/utils/version.py,sha256=NT2H9l-oIRCYhV_yjf5UjkadoJQ0IN4eLl8y__pyTPc,3001
|
|
20
|
-
yostlabs-2025.10.29.
|
|
21
|
-
yostlabs-2025.10.29.
|
|
22
|
-
yostlabs-2025.10.29.
|
|
23
|
-
yostlabs-2025.10.29.
|
|
20
|
+
yostlabs-2025.10.29.2.dist-info/METADATA,sha256=PYSyY8PEl5MCKO8oifmd2GHPjyzpNtwn8OW5z9gyD8Y,2778
|
|
21
|
+
yostlabs-2025.10.29.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
yostlabs-2025.10.29.2.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
|
|
23
|
+
yostlabs-2025.10.29.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|