Metadata-Version: 2.4
Name: hex_flow_node_robot
Version: 0.0.1
Summary: Hex Flow Node for Robot Control (Archer Y6, Firefly Y6, Hello Y6)
Author-email: Dong Zhaorui <847235539@qq.com>
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: hex_flow_core<0.1.0,>=0.0.0
Requires-Dist: hex_driver_robot<0.2.0,>=0.1.0
Requires-Dist: hex_util_msg<0.1.0,>=0.0.0
Requires-Dist: hex_util_runtime<0.1.0,>=0.0.0
Dynamic: license-file

<h1 align="center">HEX FLOW NODE ROBOT</h1>

<p align="center">
    <a href="https://github.com/hexfellow/hex_flow_node_robot/stargazers">
        <img src="https://img.shields.io/github/stars/hexfellow/hex_flow_node_robot?style=flat-square&logo=github" />
    </a>
    <a href="https://github.com/hexfellow/hex_flow_node_robot/forks">
        <img src="https://img.shields.io/github/forks/hexfellow/hex_flow_node_robot?style=flat-square&logo=github" />
    </a>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <a href="https://github.com/hexfellow/hex_flow_node_robot/issues">
        <img src="https://img.shields.io/github/issues/hexfellow/hex_flow_node_robot?style=flat-square&logo=github" />
    </a>
</p>

---

# 📖 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.0
  - `hex_driver_robot` >= 0.1.0, < 0.2.0
  - `hex_util_msg` >= 0.0.0, < 0.1.0
  - `hex_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:

```bash
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](https://raw.githubusercontent.com/hexfellow/hex-flow/main/install.sh).

## Install `hex-flow-node-robot` from PyPI

```bash
pip install hex_flow_node_robot
```

## Install `hex-flow-node-robot` from source

We provide a [venv.sh](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](https://docs.astral.sh/uv/getting-started/installation/).

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Then you can use our [venv.sh](venv.sh) to create a virtual environment with all dependencies installed:

```bash
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

```python
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

```python
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

```python
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/`](example/) directory. Each script prints a launch YAML to stdout, intended for use with `hexflow run`:

### Archer Y6

```bash
# 500 Hz control loop, 10 Hz test display
hexflow run example/archer_y6_test.launch.py
```

### Firefly Y6

```bash
# 500 Hz control loop, 10 Hz test display
hexflow run example/firefly_y6_test.launch.py
```

### Hello Y6

```bash
# 500 Hz control loop, 10 Hz test display
hexflow run example/hello_y6_test.launch.py
```

# YAML Examples

### Archer Y6 (500 Hz)

```yaml
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)

```yaml
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)

```yaml
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_state` contains only `jnt_pos` and `jnt_vel`; `jnt_eff`, `pose_pos`, and `pose_quat` are omitted.

Schema: [`msgs/msg_robot/arm_state.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/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`](https://github.com/hexfellow/hex_util_msg/blob/main/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`](https://github.com/hexfellow/hex_util_msg/blob/main/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`](https://github.com/hexfellow/hex_util_msg/blob/main/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`](https://github.com/hexfellow/hex_util_msg/blob/main/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`](https://github.com/hexfellow/hex_util_msg/blob/main/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:

1. **Parameter construction** — reads environment variables and builds a `HexRobot*Params` object specific to the robot model.
2. **Callback registration** — creates a `NodeCallback` node and registers two robot state callbacks: `robot_arm_state_cb` (invoked on arm state update) and `robot_grip_state_cb` / `robot_grip_joy_cb` (invoked on gripper/joy state update). Each callback builds the corresponding FlatBuffer message and publishes it via `node.pub()`.
3. **Control subscription** — subscribes to `arm_ctrl` and `grip_ctrl` (or `grip_led_ctrl` for Hello Y6) topics. Received samples are parsed from FlatBuffer and dispatched by control mode to the appropriate driver command method.
4. **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](LICENSE).

# 🌟 Star History

[![Star History Chart](https://api.star-history.com/svg?repos=hexfellow/hex_flow_node_robot&type=Date)](https://star-history.com/#hexfellow/hex_flow_node_robot&Date)

# 👥 Contributors

<a href="https://github.com/hexfellow/hex_flow_node_robot/graphs/contributors">
    <img src="https://contrib.rocks/image?repo=hexfellow/hex_flow_node_robot" />
</a>
