Hex Flow Node for Robot Control (Archer Y6, Firefly Y6, Hello Y6)
Project description
HEX FLOW NODE ROBOT
📖 Overview
What is hex_flow_node_robot
hex_flow_node_robot provides hex-flow nodes for controlling HEXFELLOW robot arms (Archer Y6, Firefly Y6, Hello Y6). Each driver node bridges a hardware robot to the Zenoh pub/sub network via hex_flow_core, publishing state topics from hex_driver_robot callbacks and subscribing to control topics, using FlatBuffer messages from hex_util_msg. Test nodes provide terminal-based dashboards for live state visualization and command injection.
What problem it solves
- Hardware abstraction: Wraps robot-specific network protocols (UDP-based control) into structured, topic-based state/control interfaces over Zenoh.
- Bidirectional bridge: Publishes real-time robot state (joint positions, velocities, efforts, end-effector pose) and subscribes to control commands with multiple control modes (MIT, compensation, position, pose, trajectory planning).
- Flexible deployment: Supports host/port configuration, topic remapping, and control parameter tuning via environment variables — no code changes needed to adapt to different robot setups.
- Integrated testing: Ships with terminal-based test nodes for validating robot state and control without additional tools.
Target users
- Engineers integrating HEXFELLOW robot arms (Archer Y6, Firefly Y6, Hello Y6) into automated systems.
- Researchers running robot control experiments with the hex-flow framework.
- Developers building on the hex-flow framework who need a working robot control layer.
Nodes
| Node | Description | Publishes | Subscribes |
|---|---|---|---|
hex-flow-robot-archer-y6 |
Archer Y6 arm + gripper driver | arm_state, grip_state |
arm_ctrl, grip_ctrl |
hex-flow-robot-archer-y6-test |
Terminal viewer for Archer Y6 | arm_ctrl, grip_ctrl |
arm_state, grip_state |
hex-flow-robot-firefly-y6 |
Firefly Y6 arm + gripper driver | arm_state, grip_state |
arm_ctrl, grip_ctrl |
hex-flow-robot-firefly-y6-test |
Terminal viewer for Firefly Y6 | arm_ctrl, grip_ctrl |
arm_state, grip_state |
hex-flow-robot-hello-y6 |
Hello Y6 arm + grip joy + LED driver | arm_state, grip_joy |
grip_led_ctrl |
hex-flow-robot-hello-y6-test |
Terminal viewer for Hello Y6 | grip_led_ctrl |
arm_state, grip_joy |
📦 Installation
Requirements
- Python >= 3.10
- Core dependencies:
hex_flow_core>= 0.0.0, < 0.1.0hex_driver_robot>= 0.1.0, < 0.2.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:
sudo apt update
sudo apt install -y curl gpg
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
For other systems, please install zenohd yourself, then run the install script.
Install hex-flow-node-robot from PyPI
pip install hex_flow_node_robot
Install hex-flow-node-robot from source
We provide a venv.sh script to create a virtual environment with all dependencies installed. However, you need to install uv first. For uv installation, please refer to uv official installation guide.
curl -LsSf https://astral.sh/uv/install.sh | sh
Then you can use our venv.sh to create a virtual environment with all dependencies installed:
git clone https://github.com/hexfellow/hex_flow_node_robot.git
cd hex_flow_node_robot
./venv.sh
📑 Python Config API
The package provides helper functions that return NodeConfig objects for easy integration into your LaunchConfig.
Archer Y6
from hex_flow_core import LaunchConfig
from hex_flow_node_robot import (
default_robot_archer_y6_node,
default_robot_archer_y6_test_node,
)
config = LaunchConfig(
local_only=True,
enable_tui=True,
log_to_file=True,
save_path="/tmp/robot_archer_y6.launch.yml",
)
nodes = {
"robot_archer_y6":
default_robot_archer_y6_node(
name="robot_archer_y6",
host="172.18.23.197",
port=8439,
ctrl_rate=500,
state_buffer_size=200,
sens_ts=True,
grip_type="gp80",
pose_end_in_flange="0.187,0.0,0.0,1.0,0.0,0.0,0.0",
required=True,
hidden=True,
remap_dict={
"arm_state": "robot_archer_y6/arm_state",
"grip_state": "robot_archer_y6/grip_state",
"arm_ctrl": "robot_archer_y6/arm_ctrl",
"grip_ctrl": "robot_archer_y6/grip_ctrl",
},
),
"test_archer_y6":
default_robot_archer_y6_test_node(
name="test_archer_y6",
rate_hz=10,
arm_ctrl_mode="pos",
grip_ctrl_mode="pos",
required=False,
remap_dict={
"arm_state": "robot_archer_y6/arm_state",
"grip_state": "robot_archer_y6/grip_state",
"arm_ctrl": "robot_archer_y6/arm_ctrl",
"grip_ctrl": "robot_archer_y6/grip_ctrl",
},
),
}
config.set_nodes(nodes)
print(config.export())
default_robot_archer_y6_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"robot_archer_y6" |
Node name and remap prefix |
host |
str |
"192.168.1.100" |
Robot IP address |
port |
int |
8439 |
Robot UDP port |
ctrl_rate |
float |
500.0 |
Control loop rate in Hz |
state_buffer_size |
int |
200 |
Internal state buffer size |
sens_ts |
bool |
True |
Use sensor timestamps in published messages |
grip_type |
str |
"gp80" |
Gripper model type |
pose_end_in_flange |
str |
"0.187,0.0,0.0,1.0,0.0,0.0,0.0" |
End-effector pose in flange frame (7-DOF vector) |
required |
bool |
True |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {name}/* |
default_robot_archer_y6_test_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"test_archer_y6" |
Node name |
rate_hz |
float |
10.0 |
Display refresh rate in Hz |
arm_ctrl_mode |
str |
"pos" |
Arm control mode (mit, pos, pose) |
grip_ctrl_mode |
str |
"pos" |
Gripper control mode (mit, pos) |
required |
bool |
False |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {robot_name}/* |
robot_name |
str |
"robot_archer_y6" |
Robot node to subscribe/publish to |
Firefly Y6
from hex_flow_core import LaunchConfig
from hex_flow_node_robot import (
default_robot_firefly_y6_node,
default_robot_firefly_y6_test_node,
)
config = LaunchConfig(
local_only=True,
enable_tui=True,
log_to_file=True,
save_path="/tmp/robot_firefly_y6.launch.yml",
)
nodes = {
"robot_firefly_y6":
default_robot_firefly_y6_node(
name="robot_firefly_y6",
host="172.18.23.197",
port=8439,
ctrl_rate=500,
state_buffer_size=200,
sens_ts=True,
grip_type="gp80",
pose_end_in_flange="0.187,0.0,0.0,1.0,0.0,0.0,0.0",
required=True,
hidden=True,
remap_dict={
"arm_state": "robot_firefly_y6/arm_state",
"grip_state": "robot_firefly_y6/grip_state",
"arm_ctrl": "robot_firefly_y6/arm_ctrl",
"grip_ctrl": "robot_firefly_y6/grip_ctrl",
},
),
"test_firefly_y6":
default_robot_firefly_y6_test_node(
name="test_firefly_y6",
rate_hz=10,
arm_ctrl_mode="pos",
grip_ctrl_mode="pos",
required=False,
remap_dict={
"arm_state": "robot_firefly_y6/arm_state",
"grip_state": "robot_firefly_y6/grip_state",
"arm_ctrl": "robot_firefly_y6/arm_ctrl",
"grip_ctrl": "robot_firefly_y6/grip_ctrl",
},
),
}
config.set_nodes(nodes)
print(config.export())
default_robot_firefly_y6_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"robot_firefly_y6" |
Node name and remap prefix |
host |
str |
"192.168.1.100" |
Robot IP address |
port |
int |
8439 |
Robot UDP port |
ctrl_rate |
float |
500.0 |
Control loop rate in Hz |
state_buffer_size |
int |
200 |
Internal state buffer size |
sens_ts |
bool |
True |
Use sensor timestamps in published messages |
grip_type |
str |
"gp80" |
Gripper model type |
pose_end_in_flange |
str |
"0.187,0.0,0.0,1.0,0.0,0.0,0.0" |
End-effector pose in flange frame (7-DOF vector) |
required |
bool |
True |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {name}/* |
default_robot_firefly_y6_test_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"test_firefly_y6" |
Node name |
rate_hz |
float |
10.0 |
Display refresh rate in Hz |
arm_ctrl_mode |
str |
"pos" |
Arm control mode (mit, pos, pose) |
grip_ctrl_mode |
str |
"pos" |
Gripper control mode (mit, pos) |
required |
bool |
False |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {robot_name}/* |
robot_name |
str |
"robot_firefly_y6" |
Robot node to subscribe/publish to |
Hello Y6
from hex_flow_core import LaunchConfig
from hex_flow_node_robot import (
default_robot_hello_y6_node,
default_robot_hello_y6_test_node,
)
config = LaunchConfig(
local_only=True,
enable_tui=True,
log_to_file=True,
save_path="/tmp/robot_hello_y6.launch.yml",
)
nodes = {
"robot_hello_y6":
default_robot_hello_y6_node(
name="robot_hello_y6",
host="172.18.8.96",
port=9439,
ctrl_rate=500,
state_buffer_size=200,
sens_ts=True,
led_buffer_size=10,
required=True,
hidden=True,
remap_dict={
"arm_state": "robot_hello_y6/arm_state",
"grip_joy": "robot_hello_y6/grip_joy",
"grip_led_ctrl": "robot_hello_y6/grip_led_ctrl",
},
),
"test_hello_y6":
default_robot_hello_y6_test_node(
name="test_hello_y6",
rate_hz=10,
required=False,
remap_dict={
"arm_state": "robot_hello_y6/arm_state",
"grip_joy": "robot_hello_y6/grip_joy",
"grip_led_ctrl": "robot_hello_y6/grip_led_ctrl",
},
),
}
config.set_nodes(nodes)
print(config.export())
default_robot_hello_y6_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"robot_hello_y6" |
Node name and remap prefix |
host |
str |
"192.168.1.100" |
Robot IP address |
port |
int |
8439 |
Robot UDP port |
ctrl_rate |
float |
500.0 |
Control loop rate in Hz |
state_buffer_size |
int |
200 |
Internal state buffer size |
sens_ts |
bool |
True |
Use sensor timestamps in published messages |
led_buffer_size |
int |
10 |
LED command buffer size |
required |
bool |
True |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {name}/* |
default_robot_hello_y6_test_node
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
"test_hello_y6" |
Node name |
rate_hz |
float |
10.0 |
Display refresh rate in Hz |
required |
bool |
False |
Required for launch |
hidden |
bool |
False |
Hidden node |
remap_dict |
dict |
None |
Custom remap; defaults to {robot_name}/* |
robot_name |
str |
"robot_hello_y6" |
Robot node to subscribe/publish 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:
Archer Y6
# 500 Hz control loop, 10 Hz test display
hexflow run example/archer_y6_test.launch.py
Firefly Y6
# 500 Hz control loop, 10 Hz test display
hexflow run example/firefly_y6_test.launch.py
Hello Y6
# 500 Hz control loop, 10 Hz test display
hexflow run example/hello_y6_test.launch.py
YAML Examples
Archer Y6 (500 Hz)
nodes:
- name: robot_archer_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-archer-y6
required: true
hidden: true
remap:
arm_state: robot_archer_y6/arm_state
grip_state: robot_archer_y6/grip_state
arm_ctrl: robot_archer_y6/arm_ctrl
grip_ctrl: robot_archer_y6/grip_ctrl
env:
HOST: "172.18.23.197"
PORT: "8439"
CTRL_RATE: "500"
STATE_BUFFER_SIZE: "200"
SEN_TS: "True"
GRIP_TYPE: "gp80"
POSE_END_IN_FLANGE: "0.187,0.0,0.0,1.0,0.0,0.0,0.0"
- name: test_archer_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-archer-y6-test
required: false
remap:
arm_state: robot_archer_y6/arm_state
grip_state: robot_archer_y6/grip_state
arm_ctrl: robot_archer_y6/arm_ctrl
grip_ctrl: robot_archer_y6/grip_ctrl
env:
RATE_HZ: "10"
ARM_CTRL_MODE: "pos"
GRIP_CTRL_MODE: "pos"
Firefly Y6 (500 Hz)
nodes:
- name: robot_firefly_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-firefly-y6
required: true
hidden: true
remap:
arm_state: robot_firefly_y6/arm_state
grip_state: robot_firefly_y6/grip_state
arm_ctrl: robot_firefly_y6/arm_ctrl
grip_ctrl: robot_firefly_y6/grip_ctrl
env:
HOST: "172.18.23.197"
PORT: "8439"
CTRL_RATE: "500"
STATE_BUFFER_SIZE: "200"
SEN_TS: "True"
GRIP_TYPE: "gp80"
POSE_END_IN_FLANGE: "0.187,0.0,0.0,1.0,0.0,0.0,0.0"
- name: test_firefly_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-firefly-y6-test
required: false
remap:
arm_state: robot_firefly_y6/arm_state
grip_state: robot_firefly_y6/grip_state
arm_ctrl: robot_firefly_y6/arm_ctrl
grip_ctrl: robot_firefly_y6/grip_ctrl
env:
RATE_HZ: "10"
ARM_CTRL_MODE: "pos"
GRIP_CTRL_MODE: "pos"
Hello Y6 (500 Hz)
nodes:
- name: robot_hello_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-hello-y6
required: true
hidden: true
remap:
arm_state: robot_hello_y6/arm_state
grip_joy: robot_hello_y6/grip_joy
grip_led_ctrl: robot_hello_y6/grip_led_ctrl
env:
HOST: "172.18.8.96"
PORT: "9439"
CTRL_RATE: "500"
STATE_BUFFER_SIZE: "200"
SEN_TS: "True"
LED_BUFFER_SIZE: "10"
- name: test_hello_y6
build: pip install hex_flow_node_robot
run: hex-flow-robot-hello-y6-test
required: false
remap:
arm_state: robot_hello_y6/arm_state
grip_joy: robot_hello_y6/grip_joy
grip_led_ctrl: robot_hello_y6/grip_led_ctrl
env:
RATE_HZ: "10"
Message Types (FlatBuffer)
All topics use FlatBuffer messages from hex_util_msg.msg_robot.
arm_state — HexArmState
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
jnt_pos |
float64 |
Joint positions (rad) |
jnt_vel |
float64 |
Joint velocities (rad/s) |
jnt_eff |
float64 |
Joint efforts (Nm) — Archer Y6 / Firefly Y6 |
pose_pos |
float64 |
End-effector position (m) — Archer Y6 / Firefly Y6 |
pose_quat |
float64 |
End-effector orientation (quaternion) — Archer Y6 / Firefly Y6 |
Note: For Hello Y6, the published
arm_statecontains onlyjnt_posandjnt_vel;jnt_eff,pose_pos, andpose_quatare omitted.
Schema: msgs/msg_robot/arm_state.fbs
grip_state — HexGripState
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
jnt_pos |
float64 |
Gripper joint position |
jnt_vel |
float64 |
Gripper joint velocity |
jnt_eff |
float64 |
Gripper joint effort |
Schema: msgs/msg_robot/grip_state.fbs
arm_ctrl — HexArmCtrl
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
ctrl_mode |
uint8 |
Control mode enum (mit, comp, pos, pose, pos_plan, pose_plan) |
jnt_pos |
float64 |
Target joint positions |
jnt_vel |
float64 |
Target joint velocities |
pose_pos |
float64 |
Target end-effector position |
pose_quat |
float64 |
Target end-effector orientation |
mit_tau |
float64 |
Feed-forward torque (MIT mode) |
mit_kp |
float64 |
Stiffness gains |
mit_kd |
float64 |
Damping gains |
lim_err |
float64 |
Position error limit for safety |
Schema: msgs/msg_robot/arm_ctrl.fbs
grip_ctrl — HexGripCtrl
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
ctrl_mode |
uint8 |
Control mode enum (mit, comp, pos, force) |
jnt_pos |
float64 |
Target gripper position |
jnt_vel |
float64 |
Target gripper velocity |
mit_tau |
float64 |
Feed-forward torque (MIT mode) |
mit_kp |
float64 |
Stiffness gains |
mit_kd |
float64 |
Damping gains |
lim_err |
float64 |
Position error limit for safety |
Schema: msgs/msg_robot/grip_ctrl.fbs
grip_led_ctrl — HexRgbCtrl
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
r |
uint8 |
Red channel LED values |
g |
uint8 |
Green channel LED values |
b |
uint8 |
Blue channel LED values |
Schema: msgs/msg_robot/rgb_ctrl.fbs
grip_joy — HexTeleopHello
| Field | Type | Description |
|---|---|---|
ts_ns |
int64 |
Timestamp in nanoseconds |
trigger |
float64 |
Trigger analog value |
axis_x |
float64 |
Joystick X-axis |
axis_y |
float64 |
Joystick Y-axis |
btn_w |
bool |
Button W state |
btn_x |
bool |
Button X state |
btn_y |
bool |
Button Y state |
btn_z |
bool |
Button Z state |
Schema: msgs/msg_teleop/teleop_hello.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 |
Robot Driver Nodes (archer-y6 / firefly-y6 / hello-y6)
| Variable | Type | Default | Description |
|---|---|---|---|
HOST |
str |
"192.168.1.100" |
Robot IP address |
PORT |
int |
8439 |
Robot UDP port |
CTRL_RATE |
float |
500.0 |
Control loop rate in Hz |
STATE_BUFFER_SIZE |
int |
200 |
Internal state buffer size |
SEN_TS |
bool |
True |
Use sensor timestamps in published messages |
Archer Y6 / Firefly Y6 Only
| Variable | Type | Default | Description |
|---|---|---|---|
GRIP_TYPE |
str |
"gp80" |
Gripper model type |
POSE_END_IN_FLANGE |
str |
"0.187,0.0,0.0,1.0,0.0,0.0,0.0" |
End-effector pose in flange frame |
Hello Y6 Only
| Variable | Type | Default | Description |
|---|---|---|---|
LED_BUFFER_SIZE |
int |
10 |
LED command buffer size |
Test Nodes (archer-y6-test / firefly-y6-test / hello-y6-test)
| Variable | Type | Default | Description |
|---|---|---|---|
RATE_HZ |
float |
10.0 |
Display refresh rate in Hz |
Archer Y6 / Firefly Y6 Test Only
| Variable | Type | Default | Description |
|---|---|---|---|
ARM_CTRL_MODE |
str |
"pos" |
Arm control mode (mit, pos, pose) |
GRIP_CTRL_MODE |
str |
"pos" |
Gripper control mode (mit, pos) |
Architecture
Both Archer Y6 and Firefly Y6 driver nodes follow the same pattern; Hello Y6 is a simplified variant:
- Parameter construction — reads environment variables and builds a
HexRobot*Paramsobject specific to the robot model. - Callback registration — creates a
NodeCallbacknode and registers two robot state callbacks:robot_arm_state_cb(invoked on arm state update) androbot_grip_state_cb/robot_grip_joy_cb(invoked on gripper/joy state update). Each callback builds the corresponding FlatBuffer message and publishes it vianode.pub(). - Control subscription — subscribes to
arm_ctrlandgrip_ctrl(orgrip_led_ctrlfor Hello Y6) topics. Received samples are parsed from FlatBuffer and dispatched by control mode to the appropriate driver command method. - Rate-controlled background loop — the hardware driver (
HexRobot*Callback) runs its own control loop at the configured rate. The node's main loop simply sleeps and checks for shutdown.
Test nodes use the polling-style Node API (via node.get(topic, latest=True)) instead of callbacks, subscribing to state topics and publishing control commands at a lower refresh rate (default 10 Hz).
This architecture decouples the robot hardware interface from application logic, allowing control nodes to run on separate machines connected via Zenoh.
📄 License
Apache License 2.0. See LICENSE.
🌟 Star History
👥 Contributors
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_robot-0.0.1.tar.gz.
File metadata
- Download URL: hex_flow_node_robot-0.0.1.tar.gz
- Upload date:
- Size: 25.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a04a50353a7589550e1c698ffc6267e224520c27327cf803c065e432c54efb9e
|
|
| MD5 |
36aa97b053dbb5925f6e8fee8e1dcbd6
|
|
| BLAKE2b-256 |
b8642492ef5c7e337502f4ec3bb61cf76984c14ffa53618da66983f3deb79733
|
File details
Details for the file hex_flow_node_robot-0.0.1-py3-none-any.whl.
File metadata
- Download URL: hex_flow_node_robot-0.0.1-py3-none-any.whl
- Upload date:
- Size: 22.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 |
c30fe3926720dffe223795a3ccee0f8764312815e5bed8bada520cadcd0d8d11
|
|
| MD5 |
fd0fb93c86a2bf146ec08f65c45400e7
|
|
| BLAKE2b-256 |
bdc0261a1a3ae3b723a216de12550b9648b2ed1b255d006a2b9224bd09f9035f
|