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.
@@ -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, interval=5):
99
+ def __init__(self, desired_scan_time=5):
98
100
  self.enabled = False
99
101
  self.done = True
100
102
 
101
- self.nearby = None
102
- self.thread = None
103
- self.updated = False
104
- self.duration = int(interval / 1.2)
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: return
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
- nearby = bluetooth.discover_devices(duration=self.duration, lookup_names=True, lookup_class=True)
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.1
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=xuENquGbc28QIfvHejSs_DwIeAZCIKxL2t9ba-aZOlU,6271
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.1.dist-info/METADATA,sha256=iOLa5qTNbhgUe5wLumLKPw4UBtJUIcc1Jwb8un9LLcs,2778
21
- yostlabs-2025.10.29.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- yostlabs-2025.10.29.1.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
23
- yostlabs-2025.10.29.1.dist-info/RECORD,,
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,,