Skip to content

Examples

01. Basic

  • File: 01_basic.py
  • Summary: Basic example: Read SpaceMouse input with context manager.
  • Run: python examples/01_basic.py
examples/01_basic.py
"""Basic example: Read SpaceMouse input with context manager.

This is the simplest way to use PySpaceMouse. The context manager
ensures the device is properly closed when you're done.
"""

import pyspacemouse
from pyspacemouse import AxisConvention

# Using context manager (recommended)
with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device:
    print(f"Connected to: {device.name}")
    print("Move the SpaceMouse to see values (Ctrl+C to exit)")

    while True:
        state = device.read()

        if state.has_motion():
            print(
                f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} "
                f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}"
            )

02. Callbacks

  • File: 02_callbacks.py
  • Summary: Callbacks example: React to button presses and axis movements.
  • Run: python examples/02_callbacks.py
examples/02_callbacks.py
"""Callbacks example: React to button presses and axis movements.

This example shows how to use callbacks for:
- Button presses (individual or combinations)
- Axis movements with filtering
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention


# Button callbacks receive (state, buttons, pressed_buttons)
def on_button_left(state, buttons, pressed):
    print("LEFT button pressed!")


def on_button_right(state, buttons, pressed):
    print("RIGHT button pressed!")


def on_both_buttons(state, buttons, pressed):
    print("BOTH buttons pressed together!")


# General button callback for any button change
def on_any_button(state, buttons):
    active = [i for i, b in enumerate(buttons) if b]
    if active:
        print(f"Buttons active: {active}")


# Configure button callbacks
button_callbacks = [
    pyspacemouse.ButtonCallback(0, on_button_left),
    pyspacemouse.ButtonCallback(1, on_button_right),
    pyspacemouse.ButtonCallback([0, 1], on_both_buttons),  # Both at once
]

# Open with callbacks
with pyspacemouse.open(
    axis_convention=AxisConvention.HID_Z_UP,
    dof_callback=pyspacemouse.print_state,  # Built-in DOF printer
    button_callback=on_any_button,
    button_callbacks=button_callbacks,
) as device:
    print(f"Connected to: {device.name}")
    print("Move the SpaceMouse or press buttons (Ctrl+C to exit)")
    print()

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

03. Multi Device

  • File: 03_multi_device.py
  • Summary: Multi-device example: Connect to multiple SpaceMouse devices.
  • Run: python examples/03_multi_device.py
examples/03_multi_device.py
"""Multi-device example: Connect to multiple SpaceMouse devices.

This example shows how to open two SpaceMouse devices simultaneously,
useful for dual-hand control or controlling multiple robots.
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention


def main():
    # First, discover connected devices
    connected = pyspacemouse.get_connected_devices_by_path()
    print(f"Found {len(connected)} spacemouse device(s): {list(connected.values())}")

    if len(connected) < 2:
        print("This example requires 2 SpaceMouse devices connected.")
        print("Tip: Use a 3Dconnexion Universal Receiver with device_index parameter")
        return

    # Arbitrarily take the first two devices found
    path0 = list(connected.keys())[0]
    path1 = list(connected.keys())[1]

    # Open two devices by path
    with pyspacemouse.open_by_path(path0, axis_convention=AxisConvention.HID_Z_UP) as left_hand:
        with pyspacemouse.open_by_path(
            path1, axis_convention=AxisConvention.HID_Z_UP
        ) as right_hand:
            print(f"Left hand:  {left_hand.name}")
            print(f"Right hand: {right_hand.name}")
            print()
            print("Move both devices (Ctrl+C to exit)")

            while True:
                left = left_hand.read()
                right = right_hand.read()

                if left.has_motion() or right.has_motion():
                    print(
                        f"Left: x={left.x:+.2f} y={left.y:+.2f} z={left.z:+.2f}  |  "
                        f"Right: x={right.x:+.2f} y={right.y:+.2f} z={right.z:+.2f}"
                    )

                time.sleep(0.01)


if __name__ == "__main__":
    main()

04. Open By Path

  • File: 04_open_by_path.py
  • Summary: Open by path example: Connect to a specific HID device by filesystem path.
  • Run: python examples/04_open_by_path.py
examples/04_open_by_path.py
"""Open by path example: Connect to a specific HID device by filesystem path.

This is useful when you need to ensure you always connect to the same
physical device, regardless of enumeration order.

Note: Device paths vary by OS:
- Linux: /dev/hidraw0, /dev/hidraw1, etc.
- macOS: /dev/hidraw0 or similar (may require special setup)
- Windows: Uses different path format
"""

import pyspacemouse
from pyspacemouse import AxisConvention


def main():
    # First, list all HID devices to find the path you need
    print("All HID devices:")
    for product, manufacturer, vid, pid in pyspacemouse.get_all_hid_devices():
        print(f"  {product or 'Unknown'} by {manufacturer or 'Unknown'}")
        print(f"    VID: {vid:#06x}, PID: {pid:#06x}")
    print()

    # Example: Open by specific path (Linux example)
    # Replace with actual path from your system
    device_path = "/dev/hidraw0"

    try:
        with pyspacemouse.open_by_path(
            device_path,
            axis_convention=AxisConvention.HID_Z_UP,
        ) as device:
            print(f"Connected to: {device.name} at {device_path}")
            print("Move the device (Ctrl+C to exit)")
            print()

            while True:
                state = device.read()
                if state.has_motion():
                    print(
                        f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} "
                        f"r={state.roll:+.2f} p={state.pitch:+.2f} y={state.yaw:+.2f}"
                    )

    except FileNotFoundError as e:
        print(f"Device path not found: {e}")
        print("Try running with a valid device path for your system.")
    except ValueError as e:
        print(f"Device not supported: {e}")


if __name__ == "__main__":
    main()

05. Discovery

  • File: 05_discovery.py
  • Summary: Device discovery example: List and inspect available devices.
  • Run: python examples/05_discovery.py
examples/05_discovery.py
"""Device discovery example: List and inspect available devices.

This example shows how to discover what SpaceMouse devices are
connected and what device types are supported.
"""

import pyspacemouse


def main():
    print("=" * 60)
    print("PySpaceMouse Device Discovery")
    print("=" * 60)
    print()

    # 1. List connected SpaceMouse devices
    print("Connected SpaceMouse devices:")
    connected = pyspacemouse.get_connected_devices_by_path()
    if connected:
        for name in connected.values():
            print(f"  ✓ {name}")
    else:
        print("  (none found)")
    print()

    # 2. List all supported device types
    print("Supported device types:")
    supported = pyspacemouse.get_supported_devices()
    for supported_name, vid, pid in supported:
        # Check if this device type is connected
        status = " "
        path_if_connected = ""
        for path, name in connected.items():
            if name == supported_name:
                status = "✓"
                path_if_connected = f"   (path: {path})"
        print(
            f"  [{status}] {supported_name} (VID: {vid:#06x}, PID: {pid:#06x}){path_if_connected}"
        )
    print()

    # 3. List ALL HID devices (for debugging)
    print("All HID devices on system:")
    all_hid = pyspacemouse.get_all_hid_devices()
    if all_hid:
        for product, manufacturer, vid, pid in all_hid:
            product = product or "Unknown"
            manufacturer = manufacturer or "Unknown"
            print(f"  - {product} by {manufacturer}")
            print(f"      VID: {vid:#06x}, PID: {pid:#06x}")
    else:
        print("  (none found - is hidapi installed?)")
    print()

    # 4. Show device specs (advanced)
    print("Device specifications loaded from TOML:")
    specs = pyspacemouse.get_device_specs()
    print(f"  {len(specs)} device types configured")
    print()


if __name__ == "__main__":
    main()

06. Axis Callbacks

  • File: 06_axis_callbacks.py
  • Summary: Axis callbacks example: React to specific axis movements.
  • Run: python examples/06_axis_callbacks.py
examples/06_axis_callbacks.py
"""Axis callbacks example: React to specific axis movements.

This example shows DofCallback for per-axis handling with:
- Positive/negative direction callbacks
- Filtering (deadzone)
- Rate limiting (sleep)
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention


def on_x_positive(state, value):
    """Called when X axis moves positive (right)."""
    print(f"→ X positive: {value:+.2f}")


def on_x_negative(state, value):
    """Called when X axis moves negative (left)."""
    print(f"← X negative: {value:+.2f}")


def on_z_move(state, value):
    """Called when Z axis moves (up/down)."""
    direction = "↑ UP" if value > 0 else "↓ DOWN"
    print(f"{direction}: {abs(value):.2f}")


# Configure per-axis callbacks
dof_callbacks = [
    # X axis with separate callbacks for positive/negative
    pyspacemouse.DofCallback(
        axis="x",
        callback=on_x_positive,
        callback_minus=on_x_negative,
        filter=0.1,  # Deadzone: ignore values < 0.1
        sleep=0.1,  # Rate limit: max 10 calls/second
    ),
    # Z axis with single callback for both directions
    pyspacemouse.DofCallback(
        axis="z",
        callback=on_z_move,
        filter=0.15,
        sleep=0.05,
    ),
]

with pyspacemouse.open(
    dof_callbacks=dof_callbacks,
    axis_convention=AxisConvention.HID_Z_UP,
) as device:
    print(f"Connected to: {device.name}")
    print("Move X axis (left/right) or Z axis (up/down)")
    print("Ctrl+C to exit")
    print()

    while True:
        device.read()
        time.sleep(0.01)

07. Led

  • File: 07_led.py
  • Summary: LED control example: Blink the SpaceMouse LED.
  • Run: python examples/07_led.py
examples/07_led.py
"""LED control example: Blink the SpaceMouse LED.

This example demonstrates how to control the LED on SpaceMouse devices.
The LED will toggle on and off every 0.5 seconds.

Note: Not all devices have controllable LEDs.
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention

# Using context manager (recommended)
with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device:
    print(f"Connected to: {device.name}")
    print("LED will blink every 0.5 seconds (Ctrl+C to exit)")
    print()

    led_state = True
    while True:
        device.set_led(led_state)
        led_state = not led_state
        time.sleep(0.5)

08. Buttons

  • File: 08_buttons.py
  • Summary: Button names example: Show button names when pressed.
  • Run: python examples/08_buttons.py
examples/08_buttons.py
"""Button names example: Show button names when pressed.

This example demonstrates how to get the configured button names
from devices.toml when buttons are pressed.
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention


def on_button_change(state, buttons):
    """Print names of all currently pressed buttons."""
    # Find indices of pressed buttons
    pressed_indices = [i for i, b in enumerate(buttons) if b]

    if pressed_indices:
        # Get button names from device
        names = [device.get_button_name(i) for i in pressed_indices]
        print(f"Pressed: {', '.join(names)}")


# Open device
with pyspacemouse.open(
    button_callback=on_button_change,
    axis_convention=AxisConvention.HID_Z_UP,
) as device:
    print(f"Connected to: {device.name}")
    print(f"Device has {len(device.info.button_names)} buttons:")
    for i, name in enumerate(device.info.button_names):
        print(f"  [{i}] {name}")
    print()
    print("Press buttons to see their names (Ctrl+C to exit)")
    print()

    while True:
        device.read()
        time.sleep(0.01)

09. Invert Rotations

  • File: 09_invert_rotations.py
  • Summary: Example: Invert rotation axes
  • Run: python examples/09_invert_rotations.py
examples/09_invert_rotations.py
#!/usr/bin/env python3
"""Example: Inverting rotations

This example shows how to invert axes, in this case the rotation axes (roll, pitch, yaw).
Just for demonstration purposes, this would be pretty weird :)
"""

import time

import pyspacemouse
from pyspacemouse import AxisConvention


def example_invert_rotations():
    """Show how to invert rotation axes"""
    print("\n" + "=" * 60)
    print("Invert rotations")
    print("=" * 60)

    connected = pyspacemouse.get_connected_devices()
    if not connected:
        print("No devices connected!")
        return

    specs = pyspacemouse.get_device_specs()
    base_spec_legacy = specs[connected[0]]
    base_spec = pyspacemouse.apply_axis_convention(base_spec_legacy, AxisConvention.HID_Z_UP)

    # Invert roll, pitch, and yaw
    fixed_spec = pyspacemouse.modify_device_info(
        base_spec,
        name=f"{connected[0]} (Fixed Rotations)",
        # Translation axes (x, y, z) can also be inverted here
        invert_axes=["roll", "pitch", "yaw"],
    )

    with pyspacemouse.open(device_spec=fixed_spec) as device:
        print(f"Connected to: {device.name}")
        print("Rotations are now inverted!\n")

        for _ in range(500):
            state = device.read()
            if any([state.roll, state.pitch, state.yaw]):
                print(
                    f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} "
                    f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}"
                )
            time.sleep(0.01)


if __name__ == "__main__":
    try:
        example_invert_rotations()
    except KeyboardInterrupt:
        print("\nExiting...")

10. Custom Config Unity

  • File: 10_custom_config_unity.py
  • Summary: Example: Custom device configuration with axis remapping.
  • Run: python examples/10_custom_config_unity.py
examples/10_custom_config_unity.py
#!/usr/bin/env python3
"""Example: Custom device configuration

This example shows how to create entirely custom device configurations,
In this case for a "Spacemouse Wireless New", following the left-handed Unity convention (Z forward, X right, Y up).
If you have a totally custom HID device, you just need to know the byte layout of the device and you can create a custom configuration for it.
"""

import time

import pyspacemouse


def example_unity_convention():
    """Create entirely custom device configuration with the Unity convention."""
    print("\n" + "=" * 60)
    print("Create custom device spec (Unity convention)")
    print("=" * 60)

    # This shows how to create a completely custom device spec
    # Useful for unsupported devices or complete remapping
    custom_spec = pyspacemouse.create_device_info(
        name="CustomSpaceMouse",
        vendor_id=0x256F,  # 3Dconnexion
        product_id=0xC63A,  # SpaceMouse Wireless New
        mappings={
            # Each mapping: (channel, byte1, byte2, scale)
            # Scale: 1 = normal direction, -1 = inverted
            "x": (1, 1, 2, 1),
            "y": (1, 5, 6, -1),
            "z": (1, 3, 4, -1),
            "yaw": (1, 9, 10, 1),
            "pitch": (1, 11, 12, 1),
            "roll": (1, 7, 8, -1),
        },
        buttons={
            "LEFT": (3, 1, 0),
            "RIGHT": (3, 1, 1),
        },
    )

    print(f"Created custom Unity spec: {custom_spec.name}")
    print(f"  VID/PID: {custom_spec.vendor_id:#06x}/{custom_spec.product_id:#06x}")
    print(f"  Axes: {list(custom_spec.mappings.keys())}")
    print(f"  Buttons: {custom_spec.button_names}")

    with pyspacemouse.open(device_spec=custom_spec) as device:
        print(f"Connected to: {device.name} with custom spec")

        for _ in range(5000):
            state = device.read()
            if any([state.x, state.y, state.z, state.roll, state.pitch, state.yaw]):
                print(
                    f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} "
                    f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}"
                )
            time.sleep(0.01)


if __name__ == "__main__":
    try:
        example_unity_convention()
    except KeyboardInterrupt:
        print("\nExiting...")