Hex Flow Node for Teleoperation Input (Keyboard & Joystick)
Project description
HEX FLOW NODE TELEOP
Overview
Hex Flow nodes for reading teleoperation inputs from keyboard and joystick/gamepad using evdev. Each node maintains an internal state table updated by background threads and publishes the full state at the configured rate (default 100 Hz) over Zenoh via hex_flow_core, using FlatBuffer messages from hex_util_msg.
Nodes
| Node | Description | Publishes | Subscribes |
|---|---|---|---|
hex-flow-teleop-keyboard |
Keyboard letter-key state reader | teleop_keyboard |
- |
hex-flow-teleop-joystick |
Joystick/Gamepad button & axis state reader | teleop_joy |
- |
hex-flow-teleop-keyboard-test |
Terminal viewer for keyboard state | - | teleop_keyboard |
hex-flow-teleop-joystick-test |
Terminal viewer for joystick state | - | teleop_joy |
Prerequisites
Accessing /dev/input/* requires the current user to be in the input group:
sudo usermod -aG input $USER
# Log out and back in for the change to take effect
Installation
Requirements
- Python >= 3.10
- OS: Ubuntu (or other Linux)
- Core dependencies:
evdev>= 1.7.0hex_flow_core>= 0.0.0, < 0.1.0hex_util_msg>= 0.0.0, < 0.1.0hex_util_runtime>= 0.0.0, < 0.1.0
Install hex-flow-cli
For Ubuntu or any Debian-based system, install Zenoh and hex-flow CLI:
curl -L https://download.eclipse.org/zenoh/debian-repo/zenoh-public-key | sudo gpg --dearmor --yes --output /etc/apt/keyrings/zenoh-public-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/zenoh-public-key.gpg] https://download.eclipse.org/zenoh/debian-repo/ /" | sudo tee -a /etc/apt/sources.list > /dev/null
sudo apt update
sudo apt install zenoh curl
curl -fsSL https://raw.githubusercontent.com/hexfellow/hex-flow/main/install.sh | sh
Install from PyPI
pip install hex_flow_node_teleop
Python Config API
The package provides helper functions that return NodeConfig objects for easy integration into your LaunchConfig.
from hex_flow_core import LaunchConfig
from hex_flow_node_teleop import (
default_teleop_keyboard_node,
default_teleop_keyboard_test_node,
default_teleop_joystick_node,
default_teleop_joystick_test_node,
)
config = LaunchConfig(local_only=True, save_path="/tmp/teleop_launch.yml")
nodes = {
"teleop_keyboard": default_teleop_keyboard_node(),
"test_keyboard": default_teleop_keyboard_test_node(),
"teleop_joystick": default_teleop_joystick_node(),
"test_joystick": default_teleop_joystick_test_node(),
}
config.set_nodes(nodes)
print(config.export())
default_teleop_keyboard_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"teleop_keyboard" |
Node name and remap prefix |
device_path |
str |
"" |
/dev/input/eventN or empty for auto-detect |
rate_hz |
float |
100.0 |
Publishing rate in Hz |
required |
bool |
True |
Required for launch |
remap_dict |
dict |
None |
Custom remap; defaults to {name}/teleop_keyboard |
default_teleop_keyboard_test_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"test_keyboard" |
Node name |
rate_hz |
float |
10.0 |
Display refresh rate in Hz |
required |
bool |
False |
Required for launch |
remap_dict |
dict |
None |
Custom remap; defaults to {teleop_name}/teleop_keyboard |
teleop_name |
str |
"teleop_keyboard" |
Keyboard teleop node to subscribe to |
default_teleop_joystick_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"teleop_joystick" |
Node name and remap prefix |
device_path |
str |
"" |
/dev/input/eventN or empty for auto-detect |
rate_hz |
float |
100.0 |
Publishing rate in Hz |
required |
bool |
True |
Required for launch |
remap_dict |
dict |
None |
Custom remap; defaults to {name}/teleop_joy |
default_teleop_joystick_test_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"test_joystick" |
Node name |
rate_hz |
float |
10.0 |
Display refresh rate in Hz |
required |
bool |
False |
Required for launch |
remap_dict |
dict |
None |
Custom remap; defaults to {teleop_name}/teleop_joy |
teleop_name |
str |
"teleop_joystick" |
Joystick teleop node to subscribe to |
Examples
Ready-to-run config scripts are provided in the example/ directory. Each script prints a launch YAML to stdout, intended for use with hexflow run:
Keyboard
# Basic — auto-detect device, 100 Hz publish, 10 Hz test display
hexflow run $(python example/keyboard_basic_config.py)
# Custom — specify device path, 50 Hz publish, 5 Hz test display
hexflow run $(python example/keyboard_custom_config.py)
Joystick
# Basic — auto-detect device, 100 Hz publish, 10 Hz test display
hexflow run $(python example/joystick_basic_config.py)
# Custom — specify device path, 50 Hz publish, 5 Hz test display
hexflow run $(python example/joystick_custom_config.py)
YAML Examples
Keyboard (100 Hz)
nodes:
- name: teleop_keyboard
build: pip install hex_flow_node_teleop
run: hex-flow-teleop-keyboard
required: true
remap:
teleop_keyboard: teleop_keyboard/teleop_keyboard
env:
DEVICE_PATH: ""
RATE_HZ: "100"
- name: test_keyboard
build: pip install hex_flow_node_teleop
run: hex-flow-teleop-keyboard-test
required: false
remap:
teleop_keyboard: teleop_keyboard/teleop_keyboard
env:
RATE_HZ: "10"
Joystick / Gamepad (100 Hz)
nodes:
- name: teleop_joystick
build: pip install hex_flow_node_teleop
run: hex-flow-teleop-joystick
required: true
remap:
teleop_joy: teleop_joystick/teleop_joy
env:
DEVICE_PATH: ""
RATE_HZ: "100"
- name: test_joystick
build: pip install hex_flow_node_teleop
run: hex-flow-teleop-joystick-test
required: false
remap:
teleop_joy: teleop_joystick/teleop_joy
env:
RATE_HZ: "10"
Message Types (FlatBuffer)
All topics use FlatBuffer messages from hex_util_msg.msg_teleop.
teleop_keyboard — HexTeleopKeyboard
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
key_a … key_z |
bool |
Per-letter key state (true = pressed) |
Schema: msgs/msg_teleop/teleop_keyboard.fbs
teleop_joy — HexTeleopJoy
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
btn_x, btn_y, btn_a, btn_b |
bool |
Face buttons |
btn_lb, btn_rb |
bool |
Shoulder bumpers |
btn_lt, btn_rt |
bool |
Trigger buttons |
btn_lthumb, btn_rthumb |
bool |
Thumbstick clicks |
axis_lx, axis_ly |
float64 |
Left stick axes (normalized to [-1, 1]) |
axis_rx, axis_ry |
float64 |
Right stick axes (normalized to [-1, 1]) |
axis_lt, axis_rt |
float64 |
Trigger analog values (normalized to [-1, 1]) |
hat_x, hat_y |
float64 |
D-pad / hat switch values |
Schema: msgs/msg_teleop/teleop_joy.fbs
Environment Variables
All Nodes
| Variable | Type | Default | Description |
|---|---|---|---|
HEX_FLOW_NODE_NAME |
str |
constructor arg | Overrides node name (handled by hex_flow_core) |
HEX_FLOW_REMAP |
str |
{} |
JSON dict for topic remapping (handled by hex_flow_core) |
RUST_LOG |
str |
info |
Log level for envlog |
Teleop Nodes (keyboard / joystick)
| Variable | Type | Default | Description |
|---|---|---|---|
DEVICE_PATH |
str |
"" (auto) |
Specific /dev/input/eventN path; empty = auto-detect |
RATE_HZ |
float |
100.0 |
Publishing rate in Hz |
Test Nodes (test-keyboard / test-joystick)
| Variable | Type | Default | Description |
|---|---|---|---|
RATE_HZ |
float |
10.0 |
Display refresh rate in Hz |
Architecture
Both keyboard and joystick nodes follow the same pattern:
- Device discovery — enumerate
/dev/input/*viaevdev.list_devices()and filter by capabilities (letter keys for keyboard, analog axes for joystick). - Background reader thread — a daemon thread continuously reads
evdevevents and updates an in-memory state table protected by athreading.Lock. - FlatBuffer serialization — on each publish cycle, the node snapshots the current state, builds a FlatBuffer message (
HexTeleopKeyboardorHexTeleopJoy) with a nanosecond timestamp, and publishes the binary payload viahex_flow_core.Node.pub(). - Rate-controlled publish loop — using
HexRatefromhex_util_runtime, the node publishes at the configured rate (default 100 Hz).
Test nodes subscribe via hex_flow_core.Node.create_sub() and poll with Node.get(topic, latest=True) at a lower rate (default 10 Hz), deserializing the FlatBuffer to refresh the terminal display.
This decouples the variable-rate hardware events from the fixed-rate publish output, ensuring consistent state updates regardless of actual input event frequency.
License
Apache License 2.0. See LICENSE.
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 hex_flow_node_teleop-0.0.1a1.tar.gz.
File metadata
- Download URL: hex_flow_node_teleop-0.0.1a1.tar.gz
- Upload date:
- Size: 15.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e64de28cc528c0e24df799fdb53599ee997e6840bccf9ab7cb064904580a2a43
|
|
| MD5 |
a153abaad9762b64d1a6a733e89587df
|
|
| BLAKE2b-256 |
647b895e1929021393f5fc251c78714974ff6efbb3ff3b9e8843f361203fe2eb
|
File details
Details for the file hex_flow_node_teleop-0.0.1a1-py3-none-any.whl.
File metadata
- Download URL: hex_flow_node_teleop-0.0.1a1-py3-none-any.whl
- Upload date:
- Size: 17.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d83f51a40459a9457f24bac217d7ca9374246ed920d7aadce9914e94aac1ab1
|
|
| MD5 |
84624d8df0fc832750ea694adc0e3eb9
|
|
| BLAKE2b-256 |
7c023a2b6628efe28e6bd5c59abb6db62ed11c0a9e31fb6d6fce2131505ffca8
|