Skip to main content

Python SDK for the Digity sensorized exohand

Project description

digity SDK

Python SDK for the Digity sensorized exohand — plug in the glove, stream live sensor data in three lines.

The Digity exohand is a sensorized glove that captures real-time hand motion data: joint angles for every finger, 6-axis IMU (accelerometer + gyroscope), and 6-channel capacitive touch per node. This SDK handles the USB connection, binary protocol decoding, and threading — so you get clean, typed Python objects at ~50 Hz without any low-level plumbing.

from digity import GloveStream, AnglesSensor

with GloveStream() as stream:
    for frame in stream:
        for sensor in frame.sensors:
            if isinstance(sensor, AnglesSensor):
                print(sensor.finger, sensor.samples[-1].angles_deg)

Use cases: motion capture, rehabilitation, robotics control, gesture recognition, HCI research, data collection.


Contents


Installation

# Core SDK — sensor data in Python only
pip install digity

# SDK + real-time local dashboard
pip install "digity[viz]"

# SDK + cloud agent relay
pip install "digity[agent]"

Requirements: Python 3.9 or later · Digity exohand connected via USB.


Quick start

Plug in the glove, then run:

from digity import GloveStream, AnglesSensor, ImuSensor, TouchSensor

with GloveStream() as stream:
    for frame in stream:
        print(f"side={frame.side}  node={frame.node_id}  seq={frame.seq}")

        for sensor in frame.sensors:

            if isinstance(sensor, AnglesSensor):
                angles = sensor.samples[-1].angles_deg
                print(f"  finger {sensor.finger} angles: {angles}")

            elif isinstance(sensor, ImuSensor):
                s = sensor.samples[-1]
                print(f"  finger {sensor.finger} acc={s.acc}  gyro={s.gyro}")

            elif isinstance(sensor, TouchSensor):
                print(f"  finger {sensor.finger} touch={sensor.channels}")

The with block handles connection and cleanup automatically. Press Ctrl+C to stop.


GloveStream

GloveStream(
    port=None,       # serial port e.g. "COM3" / "/dev/ttyUSB0" — None = auto-detect
    baud=921600,     # baud rate
    *,
    host=None,       # remote host IP for ZMQ mode
    zmq_port=5555,   # ZMQ port when using host
)
Method Description
connect() Open the connection. Called automatically by with.
disconnect() Close the connection. Called automatically on with exit.
__iter__() Yields GloveFrame objects. Blocks until the next frame arrives.

Specifying the USB port — the SDK auto-detects on all platforms. If auto-detection fails:

GloveStream(port="/dev/ttyUSB0")          # Linux
GloveStream(port="/dev/tty.usbserial-0001")  # macOS
GloveStream(port="COM3")                  # Windows

Stop from another thread:

import threading
from digity import GloveStream

stream = GloveStream()
stream.connect()

def stop_after(seconds):
    import time; time.sleep(seconds)
    stream.disconnect()

threading.Thread(target=stop_after, args=(10,), daemon=True).start()

for frame in stream:
    print(frame.seq)

Data types

GloveFrame

Every iteration of GloveStream yields one GloveFrame:

Field Type Description
ts float Host timestamp (seconds since epoch) when the frame arrived
side str "right" or "left"
group str "hand" or "arm"
node_id int PCB node number on the glove
seq int Packet counter 0–65535, wraps around
sensors list[Sensor] List of sensor readings in this frame

Sensor is Union[AnglesSensor, ImuSensor, TouchSensor].


AnglesSensor

Joint angles for one finger node.

Field Type Description
finger int Finger index — 0 = thumb, 1 = index, 2 = middle, 3 = ring, 4 = pinky
com int Communication line index
samples list[AnglesSample] One or more timestamped angle readings

AnglesSample

Field Type Description
ts_us int Sensor timestamp in microseconds
angles_deg list[float] Joint angles in degrees (5 values for hand group)
if isinstance(sensor, AnglesSensor):
    latest = sensor.samples[-1]
    print(latest.angles_deg)   # e.g. [12.3, 45.1, 30.0, 5.5, 2.0]

ImuSensor

6-axis IMU (accelerometer + gyroscope) for one finger node.

Field Type Description
finger int Finger index
com int Communication line index
samples list[ImuSample] One or more timestamped IMU readings

ImuSample

Field Type Description
ts_us int Sensor timestamp in microseconds
acc tuple[int, int, int] Accelerometer x/y/z — raw i16 counts
gyro tuple[int, int, int] Gyroscope x/y/z — raw i16 counts
if isinstance(sensor, ImuSensor):
    s = sensor.samples[-1]
    print(s.acc)    # e.g. (312, -128, 16384)
    print(s.gyro)   # e.g. (5, -12, 3)

TouchSensor

6-channel capacitive touch for one finger node.

Field Type Description
finger int Finger index
com int Communication line index
ts_us int Sensor timestamp in microseconds
channels list[float] 6 values normalised 0.0–1.0
channels_raw list[int] 6 raw ADC counts 0–4095
if isinstance(sensor, TouchSensor):
    print(sensor.channels)      # [0.0, 0.82, 0.0, 0.0, 0.41, 0.0]
    print(sensor.channels_raw)  # [0, 3358, 0, 0, 1680, 0]

Type annotation helper

from digity import Sensor  # Union[AnglesSensor, ImuSensor, TouchSensor]

def process(sensor: Sensor) -> None:
    if isinstance(sensor, AnglesSensor):
        ...

Common patterns

Record data to CSV

import csv
from digity import GloveStream, AnglesSensor

with GloveStream() as stream, open("recording.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["ts", "finger", "a0", "a1", "a2", "a3", "a4"])

    for frame in stream:
        for sensor in frame.sensors:
            if isinstance(sensor, AnglesSensor):
                a = sensor.samples[-1].angles_deg
                writer.writerow([frame.ts, sensor.finger] + a)

Detect a closed fist

from digity import GloveStream, AnglesSensor

with GloveStream() as stream:
    finger_angles = {}

    for frame in stream:
        for sensor in frame.sensors:
            if isinstance(sensor, AnglesSensor):
                finger_angles[sensor.finger] = sensor.samples[-1].angles_deg

        if len(finger_angles) == 5:
            avg_flex = sum(finger_angles[f][1] for f in range(1, 5)) / 4
            if avg_flex > 60:
                print("Fist detected!")

Read a saved recording

import json
from pathlib import Path

session_dir = Path("~/digity-recordings/admin/user01_grasp_2025-04-22T10-30-00").expanduser()
meta = json.loads((session_dir / "meta.json").read_text())
print(f"Duration: {meta['duration_s']:.1f}s  Frames: {meta['frame_count']}")

with open(session_dir / "recording.jsonl") as f:
    for line in f:
        frame = json.loads(line)
        print(frame["ts"], frame["side"])

Error handling

from digity import GloveStream, GloveNotFoundError

try:
    with GloveStream() as stream:
        for frame in stream:
            ...
except GloveNotFoundError:
    print("Glove not found — check the USB cable")
except KeyboardInterrupt:
    pass

GloveNotFoundError is raised at connection time if the glove port cannot be found.


Real-time dashboard (digity[viz])

digity[viz] ships a full local dashboard that visualises live hand data in 3-D, records labelled sessions, and serves as a local relay for remote agents — no internet required.

pip install "digity[viz]"

digity-viz              # opens as a desktop window
digity-viz --port COM3  # explicit serial port
digity-viz --browser    # open in system browser instead

Or from Python:

import digity.viz
digity.viz.start()

The dashboard runs at http://localhost:5001/chiros/ and opens automatically.

Dashboard pages:

Page Description
Viewer Live 3-D hand model with real-time joint angles
Record Start/stop sessions, download .jsonl files, manage recordings
Setup Recordings folder, agent token, update checker

Session recordings are saved to ~/digity-recordings/ by default. Each session creates a directory containing meta.json (duration, frame count, task label) and recording.jsonl (one JSON frame per line at ~50 Hz).


Agent mode (digity[agent])

Stream the glove directly to the cloud dashboard — no separate agent.exe needed.

pip install "digity[agent]"

# copy your token from the dashboard → Setup page
digity-agent --token YOUR_TOKEN

# or via the digity-viz command
digity-viz --agent --token YOUR_TOKEN

# explicit port or custom server URL
digity-agent --token YOUR_TOKEN --port COM3
digity-agent --token YOUR_TOKEN --url https://app.digity.de/chiros

The agent auto-detects the glove, connects to the server, and streams frames in real time. It reconnects automatically if either connection drops.

If digity-agent is not on PATH (common with Windows Store Python), use:

python -m digity.viz --agent --token YOUR_TOKEN

Publish over the network (ZMQ)

GlovePublisher broadcasts frames over a ZMQ PUB socket so multiple processes or machines can subscribe to the same glove.

from digity import GlovePublisher

# Standalone — blocks until Ctrl+C
with GlovePublisher() as pub:
    pub.run()

# Embedded in a custom loop
from digity import GloveStream, GlovePublisher

with GlovePublisher() as pub, GloveStream() as stream:
    for frame in stream:
        pub.publish(frame)
        # ... your processing here
GlovePublisher(
    zmq_port=5555,     # port to bind
    bind="tcp://*",    # bind address
)
Method Description
start() Open ZMQ PUB socket. Returns self.
stop() Close socket.
publish(frame) Publish one frame. Non-blocking, thread-safe.
run(port, baud) Read from serial and publish. Blocks until Ctrl+C.

Remote ZMQ receive

On any machine that can reach the publisher over the network:

from digity import GloveStream

with GloveStream(host="192.168.1.10") as stream:        # default port 5555
    for frame in stream:
        print(frame.side, frame.sensors)

# Custom ZMQ port
with GloveStream(host="192.168.1.10", zmq_port=5556) as stream:
    ...

No USB connection is needed on the receiving machine.


Connection speed

The glove streams at approximately 50 Hz. Each for frame in stream call blocks until the next frame arrives (up to 1 second timeout before checking for errors).

The SDK uses a background thread and an internal queue so your processing code never stalls the serial buffer. If your code is slower than 50 Hz, frames are dropped to keep the stream live rather than accumulating memory.


Updating

pip install --upgrade digity
pip install --upgrade "digity[viz]"    # if using the dashboard
pip install --upgrade "digity[agent]"  # if using agent mode

License

© Digity. All rights reserved.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

digity-0.2.5.tar.gz (8.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

digity-0.2.5-py3-none-any.whl (8.5 MB view details)

Uploaded Python 3

File details

Details for the file digity-0.2.5.tar.gz.

File metadata

  • Download URL: digity-0.2.5.tar.gz
  • Upload date:
  • Size: 8.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.2

File hashes

Hashes for digity-0.2.5.tar.gz
Algorithm Hash digest
SHA256 a0956cccac9293f1ed8d9b23c14025ecf96ca9824302a8901f26bf12e78f6100
MD5 9f9d791ecd140e1345d2d663c17c989f
BLAKE2b-256 64289136def930af0c4fa2cdd6b9abf468e0a1d0e5a1d326972ba8315048a1dd

See more details on using hashes here.

File details

Details for the file digity-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: digity-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 8.5 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.2

File hashes

Hashes for digity-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 b0dc4445e086a1d60c08acb83b10c3d206a109f8fc62ec4905fd45d4b2b32106
MD5 a64054847bb01097d350339aecc7febd
BLAKE2b-256 44d3df885d37d104fb9e1606b1c96b47c76e31358b41b417c92b0ef50e21f630

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page