Skip to content

Commit 7af8b4f

Browse files
peterpeter
authored andcommitted
add new read_latest() function and improve documentation on USB buffering and slow read loops
1 parent 284caf6 commit 7af8b4f

12 files changed

Lines changed: 62 additions & 20 deletions

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The optional argument is the threshold used, and is applied per-axis.
4949
import pyspacemouse
5050

5151
# Context manager (recommended) - automatically closes device
52-
with pyspacemouse.open() as device:
52+
with pyspacemouse.open(nonblocking=False) as device:
5353
while True:
5454
state = device.read()
5555
if state.has_motion(0.01):
@@ -172,10 +172,10 @@ dof_callbacks = [
172172
with pyspacemouse.open(
173173
button_callbacks=button_callbacks,
174174
dof_callbacks=dof_callbacks,
175+
nonblocking=False
175176
) as device:
176177
while True:
177178
device.read() # Triggers callbacks
178-
time.sleep(0.001) # NOTE: avoid larger sleeps, which can cause data to buffer
179179
```
180180

181181
### Custom Axis Mapping
@@ -195,12 +195,30 @@ custom = pyspacemouse.modify_device_info(
195195
invert_axes=["y", "z", "roll", "yaw"], # Invert these
196196
)
197197

198-
with pyspacemouse.open(device_spec=custom) as device:
198+
with pyspacemouse.open(device_spec=custom, nonblocking=False) as device:
199199
state = device.read()
200200
```
201201

202202
See [Custom Device Configuration](./docs/mouseApi/index.md#custom-device-configuration) for full API.
203203

204+
## Blocking, Non-Blocking, sleeps, and callbacks
205+
206+
The default `open()` function uses `nonblocking=True`, which means `read()` can return without any data if none is available.
207+
However, the spacemouse device sends data very quickly and the operating system may buffer this data if it is not being read quickly enough.
208+
This means that you are very unlikely to ever _not_ get data from `read()`, even in non-blocking mode.
209+
This also means that if you have a loop that calls `read()` and it is slow, for example if you add `sleep(0.01)`, you might start to see the data start lagging behind a bit.
210+
For instance, if you poke the spacemouse it will take a few dozen/hundred milliseconds for non-zero data to be return from `read()`.
211+
This is why you see many of our examples us `nonblocking=False` and no sleep at all.
212+
This is an attempt to read as quickly as possible to avoid buffering data.
213+
If you want to minimize the effects of buffering while still operating at a lower rate, consider something like this:
214+
```python
215+
with pyspacemouse.open() as device:
216+
while True:
217+
state = device.read()
218+
if
219+
```
220+
221+
204222
## CLI
205223

206224
```bash

examples/01_basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pyspacemouse import AxisConvention
99

1010
# Using context manager (recommended)
11-
with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device:
11+
with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP, nonblocking=False) as device:
1212
print(f"Connected to: {device.name}")
1313
print("Move the SpaceMouse to see values (Ctrl+C to exit)")
1414

examples/02_callbacks.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
- Axis movements with filtering
66
"""
77

8-
import time
9-
108
import pyspacemouse
119
from pyspacemouse import AxisConvention
1210

@@ -44,11 +42,11 @@ def on_any_button(state, buttons):
4442
dof_callback=pyspacemouse.print_state, # Built-in DOF printer
4543
button_callback=on_any_button,
4644
button_callbacks=button_callbacks,
45+
nonblocking=False,
4746
) as device:
4847
print(f"Connected to: {device.name}")
4948
print("Move the SpaceMouse or press buttons (Ctrl+C to exit)")
5049
print()
5150

5251
while True:
5352
device.read() # Must call read() to process callbacks
54-
time.sleep(0.01)

examples/03_multi_device.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ def main():
3535
print("Move both devices (Ctrl+C to exit)")
3636

3737
while True:
38-
left = left_hand.read()
39-
right = right_hand.read()
38+
left = left_hand.read_latest()
39+
right = right_hand.read_latest()
4040

4141
if left.has_motion() or right.has_motion():
4242
print(
4343
f"Left: x={left.x:+.2f} y={left.y:+.2f} z={left.z:+.2f} | "
4444
f"Right: x={right.x:+.2f} y={right.y:+.2f} z={right.z:+.2f}"
4545
)
4646

47-
time.sleep(0.01)
47+
time.sleep(0.1)
4848

4949

5050
if __name__ == "__main__":

examples/04_open_by_path.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def main():
2828
try:
2929
with pyspacemouse.open_by_path(
3030
device_path,
31+
nonblocking=False,
3132
axis_convention=AxisConvention.HID_Z_UP,
3233
) as device:
3334
print(f"Connected to: {device.name} at {device_path}")

examples/05_discovery.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ def main():
2828
supported = pyspacemouse.get_supported_devices()
2929
for supported_name, vid, pid in supported:
3030
# Check if this device type is connected
31-
status = " "
31+
count = 0
3232
path_if_connected = ""
3333
for path, name in connected.items():
3434
if name == supported_name:
35-
status = "✓"
35+
count += 1
3636
path_if_connected = f" (path: {path})"
37+
count_str = " " if count == 0 else str(count)
3738
print(
38-
f" [{status}] {supported_name} (VID: {vid:#06x}, PID: {pid:#06x}){path_if_connected}"
39+
f" [{count_str}] {supported_name} (VID: {vid:#06x}, PID: {pid:#06x}){path_if_connected}"
3940
)
4041
print()
4142

examples/06_axis_callbacks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ def on_z_move(state, value):
5858

5959
while True:
6060
device.read()
61-
time.sleep(0.01)
61+
time.sleep(0.001)

examples/08_buttons.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ def on_button_change(state, buttons):
3636

3737
while True:
3838
device.read()
39-
time.sleep(0.01)
39+
time.sleep(0.001)

examples/09_invert_rotations.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def example_invert_rotations():
3434
invert_axes=["roll", "pitch", "yaw"],
3535
)
3636

37-
with pyspacemouse.open(device_spec=fixed_spec) as device:
37+
with pyspacemouse.open(device_spec=fixed_spec, nonblocking=False) as device:
3838
print(f"Connected to: {device.name}")
3939
print("Rotations are now inverted!\n")
4040

@@ -45,7 +45,6 @@ def example_invert_rotations():
4545
f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} "
4646
f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}"
4747
)
48-
time.sleep(0.01)
4948

5049

5150
if __name__ == "__main__":

pyspacemouse/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def _create_and_open_device(
139139
)
140140
mouse.open()
141141
hid_device.set_nonblocking(nonblocking)
142+
mouse._nonblocking = nonblocking
142143
return mouse
143144

144145

0 commit comments

Comments
 (0)