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 directly into your Python code 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.
Requirements
- Python 3.9 or later
- The Digity exohand connected via USB
Installation
# SDK only
pip install digity
# SDK + real-time dashboard
pip install digity[viz]
Real-time dashboard
digity[viz] includes a full web-based dashboard that visualises live hand data, records sessions, and relays data from a remote agent — all locally, no internet required.
digity-viz # opens the dashboard as a desktop window
digity-viz --port COM3 # explicit serial port
digity-viz --browser # open in system browser instead
Or launch from Python:
import digity.viz
digity.viz.start()
The dashboard runs on http://localhost:5001/chiro/ and opens automatically. No login needed — it is intended for single-user local use.
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.
Specifying the USB port
The SDK auto-detects the glove port on all platforms. If auto-detection fails, pass the port explicitly:
GloveStream(port="/dev/ttyUSB0") # Linux
GloveStream(port="/dev/tty.usbserial-0001") # macOS
GloveStream(port="COM3") # Windows
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 |
AnglesSensor
Joint angles for one finger node.
| Field | Type | Description |
|---|---|---|
finger |
int |
Finger index (0=thumb, 1=index, …, 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 a file
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[0] > 60:
print("Fist detected!")
Stop the stream 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)
Remote mode (ZMQ)
If the glove is connected to a remote machine running glove-core, you can receive frames over the network:
GloveStream(host="192.168.1.10") # default port 5555
GloveStream(host="192.168.1.10", zmq_port=5556)
No USB connection is needed on the client machine in this mode.
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.
Connection speed
The glove streams at approximately 50 Hz. Each call to for frame in stream 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 that your processing code never blocks the serial buffer. If your code is slower than 50 Hz, frames are dropped to keep the stream live rather than accumulating memory.
License
© Digity. All rights reserved.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file digity-0.1.6.tar.gz.
File metadata
- Download URL: digity-0.1.6.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c75ad7d9b243a2019191fb587c44d50dd14587c150959aac458e51ee81cd657b
|
|
| MD5 |
9956b35f6f968750039da8be7225407a
|
|
| BLAKE2b-256 |
d26fea73893afa214f599a252e66e85165d93d7e7d256048d2a854af81a5ab61
|
File details
Details for the file digity-0.1.6-py3-none-any.whl.
File metadata
- Download URL: digity-0.1.6-py3-none-any.whl
- Upload date:
- Size: 2.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0f75cd90682408f01e4df34a36d939f914b967b14979951ab389a1907304511
|
|
| MD5 |
40e94c675c2df01647c9433a0d8277c5
|
|
| BLAKE2b-256 |
df5d178aa3422d306ec959cf4a7fc62157c301844ffbbd73c56ff0481f358038
|