Skip to main content

dexter-controller

PyPI Python

Python library for the Dexter hand device. This library handles device communication, force computation, calibration, and data recording.

Installation

The recommended way to install the library is via uv. In your project add the dependency with:

uv add dexter-controller
# or use pip
pip install dexter-controller

Optional extras for reading and writing recordings:

uv add dexter-controller[reader]    # numpy, for loading recordings
uv add dexter-controller[parquet]   # pyarrow + numpy, for Parquet format support
# or use pip
pip install dexter-controller[reader]    # numpy, for loading recordings
pip install dexter-controller[parquet]   # pyarrow + numpy, for Parquet format support

Quick start

At this moment, the recommended way to connect is over Bluetooth Low Energy. The controller auto-discovers and connects to the first Dexter device in range, but you can specify a particular device by name:

import time
from dexter_controller import DexterHandController, Finger

controller = DexterHandController(use_ble=True,
    # if you want to connect to a specific device by name
    #ble_device_name="Dexter-001"
)

device_name = controller.get_device_name()
battery = controller.get_battery_level()
rate = controller.get_sampling_rate()
print(f"Device: {device_name} | Battery: {battery}% | Rate: {rate.value} Hz")

try:
    while True:
        for finger in Finger:
            data = controller.finger_data[finger]
            print(f"{finger.name}: {data.raw_data}", end="  ")
        print(end="\r", flush=True)
        time.sleep(0.02)
except KeyboardInterrupt:
    pass
finally:
    controller.close()

Force computation

Convert raw load-cell readings into calibrated 2D force vectors (Fx, Fy):

from dexter_controller import (
    DEFAULT_CALIBRATION_3CH,
    DexterHandController,
    Finger,
    ForceProcessor,
    get_default_orientation,
)

controller = DexterHandController(use_ble=True)

processors = {}
for finger in Finger:
    orientation = get_default_orientation(finger)
    processors[finger] = ForceProcessor(DEFAULT_CALIBRATION_3CH, orientation)

# In your read loop:
data = controller.finger_data[Finger.INDEX]
fx, fy = processors[Finger.INDEX].process_3(data)

Each ForceProcessor includes a TareState that lets you zero-out the baseline:

processors[Finger.INDEX].tare.start_tare(sample_count=20)

Recording data

Record sessions to disk in CSV, SQLite (default), or Parquet format:

from dexter_controller import (
    DEFAULT_CALIBRATION_3CH,
    DataRecorder,
    DexterHandController,
    Finger,
    ForceProcessor,
    RecordingMetadata,
    get_default_orientation,
)
from dexter_controller.recording_format import RecordingFormat

controller = DexterHandController(use_ble=True)

processors = {}
for finger in Finger:
    processors[finger] = ForceProcessor(
        DEFAULT_CALIBRATION_3CH, get_default_orientation(finger)
    )

recorder = DataRecorder(
    "./recordings",
    file_format=RecordingFormat.SQLITE,
    metadata=RecordingMetadata(
        device_name=controller.get_device_name(),
        sampling_rate_hz=controller.get_sampling_rate().value,
    ),
)

def on_sample(_data):
    fingers = [controller.finger_data[f] for f in Finger]
    forces = [processors[f].process_3(controller.finger_data[f]) for f in Finger]
    baselines = [(processors[f].tare.baseline_x, processors[f].tare.baseline_y) for f in Finger]
    recorder.write_sample(fingers, forces, baselines)

controller.register_finger_callback(Finger.PINKY, on_sample)

recorder.start_recording()
# ... run until done ...
recorder.stop_recording()
controller.close()

Each recording file includes session metadata.

Session metadata has the following fields:

  • session_id: Unique identifier for the recording session.
  • recording_started_at: Timestamp when the recording started.
  • recording_stopped_at: Timestamp when the recording stopped.
  • total_samples: Total number of samples recorded.
  • library_version: Version of the library used for recording.
  • timestamps: Array of timestamps for each sample.
  • device_name: Name of the device used for recording.
  • sampling_rate_hz: Sampling rate in Hz.
  • extras: Dictionary for experiment-specific metadata.

You can attach experiment-specific metadata via RecordingMetadata.extras:

metadata = RecordingMetadata(
    device_name="Dexter-001",
    sampling_rate_hz=1000,
    extras={"subject_id": "S01", "trial": "3", "condition": "baseline"},
)

Reading recordings

Load recorded sessions back for analysis:

from dexter_controller import RecordingReader

recording = RecordingReader.read("recordings/session.db")
print(f"Samples: {recording.num_rows}")
print(f"Session: {recording.metadata.session_id}")
print(f"Forces shape: {recording.forces.shape}")    # (N, 10) - Fx,Fy per finger
print(f"Raw channels: {recording.raw_channels.shape}")  # (N, 15) - 3 channels per finger

For either the Sqlite and Parquet formats, you can use Pandas or Polars to load the data for analysis.

import pandas as pd

# for Sqlite format, use:
conn = RecordingReader.open("recordings/session.db")
df = pd.read_sql("SELECT * FROM samples", conn)
print(df.head())

# or for Parquet format, use:
df = pd.read_parquet("recordings/session.parquet")
print(df.head())

You can also convert those formats to CSV for easier analysis or interoperability:

RecordingReader.to_csv("recordings/session.db")  # produces session.csv

BLE device configuration

When connected via BLE, you can read and write device settings:

from dexter_controller import SamplingRate

controller.get_device_name()                          # read name
controller.set_device_name("MyDexter")                # rename device
controller.get_sampling_rate()                        # current rate
controller.set_sampling_rate(SamplingRate.HZ_1000)    # change rate
controller.get_battery_level()                        # battery percentage

Available sampling rates: 100, 200, 400, 800, 1000, 2000 Hz.

Examples

See the example/ directory for complete, runnable scripts:

File Description
example_ble.py BLE connection with raw data streaming and event rate monitoring
example_ble_force.py BLE connection with real-time force computation using ForceProcessor
example_ble_recording.py Full recording session: BLE connect, force processing, and data recording to file
example_serial.py Serial (wired) connection with multi-port finger mapping

Serial connection (old prototype only, to be deprecated)

For wired setups using Harp load-cell boards, provide a port-to-finger mapping:

from dexter_controller import DexterHandController, Finger

mapping = {
    "COM3": [Finger.THUMB, Finger.INDEX],     # /dev/ttyUSB0 on Linux
    "COM4": [Finger.MIDDLE, Finger.RING],
    "COM5": [Finger.PINKY],
}
controller = DexterHandController(mapping)

Each serial device supports up to two fingers (8 channels, 4 per finger).

Metadata

Release files for dexter-controller 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dexter-controller 0.3.0
File Size Uploaded
dexter_controller-0.3.0.tar.gz 21.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dexter-controller 0.3.0
File Interpreter ABI Platform
dexter_controller-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 50.0 kB

Release files / dexter_controller-0.3.0.tar.gz

Download URL dexter_controller-0.3.0.tar.gz
Size 21.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2480d75943762c0bcd76cea25c542060c14895711000cff8371215bddf4430b8
BLAKE2b-256 checksum
How to use checksums
b81bbc15ffd1f4a3a8ded1fb3cc7d606a85e89d63b9eb74b5a0ed2b8b0ce8227
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / dexter_controller-0.3.0-py3-none-any.whl

Download URL dexter_controller-0.3.0-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e8856dd7999425ca4b32263245870f2fed22fa8d0083f5fbcd98f51754dac37
BLAKE2b-256 checksum
How to use checksums
097e0db9ae06381ef868944aceae468803f06f5faa021082a6b76f24f6302211
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page