Add get_connected_paths_and_names API to more clearly handle multiple devices of different types#47
Add get_connected_paths_and_names API to more clearly handle multiple devices of different types#47peter-mitrano-ar wants to merge 8 commits intomasterfrom
get_connected_paths_and_names API to more clearly handle multiple devices of different types#47Conversation
Reviewer's GuideThis PR changes get_connected_devices to return (device_path, device_name) tuples instead of plain names and updates the API consumers (examples and CLI) to use paths for device selection and display, improving support for multiple identical devices. Sequence diagram for updated device listing via CLIsequenceDiagram
actor User
participant PySpaceMouseCLI as pyspacemouse_cli
participant API as api
participant HID as hid
User->>PySpaceMouseCLI: list_spacemouse_cli()
PySpaceMouseCLI->>API: get_connected_devices()
API->>API: device_specs = get_device_specs()
API->>HID: hid.find()
HID-->>API: hid_devices
loop match_supported_devices
API->>API: compare vendor_id and product_id with device_specs
API->>API: devices_by_path[hid_device.path] = device_name
end
API-->>PySpaceMouseCLI: list(devices_by_path.items())
alt devices_found
loop for_each_device
PySpaceMouseCLI->>PySpaceMouseCLI: print device_name and device_path
end
else no_devices_found
PySpaceMouseCLI->>PySpaceMouseCLI: print no devices message
end
PySpaceMouseCLI-->>User: console output with names and paths
Flow diagram for updated get_connected_devices return structureflowchart TD
A["Start get_connected_devices"] --> B["Call get_device_specs"]
B --> C["Initialize devices_by_path as empty dict"]
C --> D["Call hid.find to get hid_devices"]
D --> E{Any hid_device remaining?}
E -->|Yes| F["Take next hid_device"]
F --> G["Compare vendor_id and product_id with each spec in device_specs"]
G --> H{Match found?}
H -->|Yes| I["Set devices_by_path[hid_device.path] = device_name"]
H -->|No| E
I --> E
E -->|No| J["Convert devices_by_path.items to list of (device_path, device_name)"]
J --> K["Return list of (device_path, device_name)"]
K --> L["End get_connected_devices"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
examples/09_custom_config.py,base_spec = specs[device_path]looks incorrect sinceget_device_specs()appears to be keyed by device name (as used elsewhere in the file); this should likely index bydevice_nameinstead to avoid a runtime KeyError. - Changing
get_connected_devices()from returning a list of names to a list of(path, name)tuples is a breaking API change; consider providing a backward-compatible helper or alias (e.g., a wrapper that returns just names) to avoid surprising existing users.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `examples/09_custom_config.py`, `base_spec = specs[device_path]` looks incorrect since `get_device_specs()` appears to be keyed by device name (as used elsewhere in the file); this should likely index by `device_name` instead to avoid a runtime KeyError.
- Changing `get_connected_devices()` from returning a list of names to a list of `(path, name)` tuples is a breaking API change; consider providing a backward-compatible helper or alias (e.g., a wrapper that returns just names) to avoid surprising existing users.
## Individual Comments
### Comment 1
<location> `examples/09_custom_config.py:39` </location>
<code_context>
# Get base spec and create modified version
- base_spec = specs[device_name]
+ base_spec = specs[device_path]
print(f"Original mappings: y scale = {base_spec.mappings['y'].scale}")
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `device_path` as the key into `specs` is likely incorrect and will raise at runtime.
In `example_modify_existing`, `specs` still comes from `pyspacemouse.get_device_specs()`, which is keyed by device name, not device path. In `example_invert_rotations` you already use `specs[device_name]`. Using `device_path` here will likely raise a `KeyError`; this should be `base_spec = specs[device_name]` to match the expected `specs` structure.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
just for making printing/sleeping in examples simpler also added a warning about sleeps
get_connected_devices API and examples to more clearly handle multiple devices of different typesget_connected_paths_and_names API to more clearly handle multiple devices of different types
There was a problem hiding this comment.
Pull request overview
Adds a new discovery API that returns device filesystem paths alongside device type names, enabling callers to uniquely identify multiple connected SpaceMouse devices of the same model.
Changes:
- Introduce
get_connected_paths_and_names()and export it viapyspacemouse.__init__. - Update CLI and examples to use the new path-aware discovery output.
- Add
SpaceMouseState.nonzero()helper and update docs/examples to use it for “only print on movement” behavior.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
pyspacemouse/api.py |
Adds get_connected_paths_and_names() returning a path→name mapping. |
pyspacemouse/__init__.py |
Exposes the new API in public exports. |
pyspacemouse/types.py |
Adds SpaceMouseState.nonzero() convenience helper. |
pyspacemouse/pyspacemouse_cli.py |
CLI now lists connected devices with paths and uses state.nonzero(). |
examples/01_basic.py |
Uses state.nonzero() (but now loops without backoff). |
examples/02_callbacks.py |
Adds guidance comment about sleep buffering. |
examples/03_multi_device.py |
Switches to opening devices by path (but now loops without backoff). |
examples/04_open_by_path.py |
Uses state.nonzero() and prints rotations (but now loops without backoff). |
examples/05_discovery.py |
Updates discovery output to incorporate paths. |
examples/09_custom_config.py |
Uses path-aware discovery and state.nonzero(); adjusts loop timing. |
README.md |
Documents new API and updates examples to use nonzero(). |
docs/README.md |
Documents new API in the docs README. |
tests/test_readme.py |
Updates loop style (but still contains top-level infinite loop). |
tests/hidapi_test.py |
Minor comment-only edit. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) as device: | ||
| while True: | ||
| device.read() # Triggers callbacks | ||
| time.sleep(0.001) # NOTE: avoid larger sleeps, which can cause data to buffer | ||
| ``` |
There was a problem hiding this comment.
This code block calls time.sleep(0.001) but the snippet does not import time, so copying it as-is will raise NameError. Add the import time in the example (or remove the sleep call if you don’t want to introduce that dependency in the snippet).
The
get_connected_devicesfunction only returns device type names, e.g.SpaceMouseCompactorSpaceMousePro. But these names do not uniquely identify the device. If you have two of the same device, you cannot know which one is which.The new function
get_connected_paths_and_names()returns both file paths and names (e.g.[('SpaceMouseCompact', '/dev/hidraw4')])For example, you can now get print out of the device names (types) and paths: