Metadata-Version: 2.4
Name: hex_flow_node_data
Version: 0.0.2
Summary: Hex Flow Node for Data Recording and Replaying
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_util_data<0.1.0,>=0.0.0
Requires-Dist: hex_util_msg<0.1.0,>=0.0.0
Requires-Dist: hex_util_runtime<0.1.0,>=0.0.0
Requires-Dist: hex_util_robot<0.1.0,>=0.0.0
Dynamic: license-file

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

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

---

# 📖 Overview

## What is `hex_flow_node_data`

`hex_flow_node_data` provides hex-flow nodes for recording and replaying robot data. It currently supports two operations: **recording** — subscribing to robot state, camera color, and camera depth topics, and writing them to Foxglove MCAP files via `hex_util_data`; and **replay** — reading MCAP files and publishing recorded data back onto the network. The record node is toggle-controlled by a boolean message on the `record` topic, enabling episode-based data collection.

## What problem it solves

- **Episode-based data collection**: Supports multiple recording episodes, each written as a separate `episode_NNNNNN.mcap` file, making it easy to organize training data for imitation learning or model-based RL.
- **Dynamic topic discovery**: Automatically classifies topics by suffix (`*state`, `*color`, `*depth`) from the `HEX_FLOW_REMAP` JSON configuration, so the same binary works with any robot configuration — no code changes needed.
- **Foxglove-native format**: Writes MCAP files compatible with Foxglove Studio for visualization and analysis.
- **Flexible recording control**: Start/stop recording via a dedicated `record` topic; integrates with keyboard teleoperation or any other trigger source.
- **Integrated testing**: Ships with a test node that publishes predefined control commands and toggles recording on the `S` key — no additional tools needed.
- **Simulation & real-robot support**: Example launch configurations demonstrate integration with both MuJoCo simulation and real Archer Y6 robots with cameras.

## Target users

- Engineers collecting demonstration data from HEXFELLOW robot systems for imitation learning or behavior cloning.
- Researchers running data acquisition pipelines (teleoperation → recording) before training policies.
- Developers building on the hex-flow framework who need a working data recording and replay layer.

## Nodes

| Node                               | Description                                    | Publishes                                                                               | Subscribes                                                                                                    |
| ---------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `hex-flow-data-record`             | Records robot state and camera data to MCAP    | —                                                                                       | `{side}_arm_state`, `{side}_grip_state`, `obj_pose`, `{cam}_color`, `{cam}_depth`, `record`                   |
| `hex-flow-data-record-test`        | Terminal-based test control for recording      | `{side}_arm_ctrl`, `{side}_grip_ctrl`, `record`                                        | `keys`                                                                                                        |
| `hex-flow-data-replay`             | Replays MCAP data back onto the network        | `{side}_arm_state`, `{side}_grip_state`, `obj_pose`, `{cam}_color`, `{cam}_depth`      | —                                                                                                             |

# 📦 Installation

## Requirements

- **Python** >= 3.10
- **OS**: Ubuntu (or other Linux)
- **Core dependencies**:
  - `hex_flow_core` >= 0.0.0, < 0.1.0
  - `hex_util_data` >= 0.0.0, < 0.1.0
  - `hex_util_msg` >= 0.0.0, < 0.1.0
  - `hex_util_runtime` >= 0.0.0, < 0.1.0
  - `hex_util_robot` >= 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-data` from PyPI

```bash
pip install hex_flow_node_data
```

## Install `hex-flow-node-data` 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 [venv.sh](venv.sh) to create a virtual environment with all dependencies installed:

```bash
git clone https://github.com/hexfellow/hex_flow_node_data.git
cd hex_flow_node_data
./venv.sh
```

# 📑 Python Config API

The package provides helper functions that return `NodeConfig` objects for easy integration into your `LaunchConfig`.

```python
from hex_flow_core import LaunchConfig
from hex_flow_node_data import (
    default_data_record_node,
    default_data_record_test_node,
    default_data_replay_node,
)
```

## `default_data_record_node`

| Parameter      | Type    | Default                       | Description                                                 |
| -------------- | ------- | ----------------------------- | ----------------------------------------------------------- |
| `name`         | `str`   | `"data_record"`               | Node name                                                   |
| `record_path`  | `str`   | `"./hex_record_data"`         | Directory path for MCAP file output                         |
| `foxglove_host`| `str`   | `"127.0.0.1"`                 | Foxglove WebSocket server host                              |
| `foxglove_port`| `int`   | `8765`                        | Foxglove WebSocket server port                              |
| `start_cnt`    | `int`   | `0`                           | Starting episode count for file naming                      |
| `required`     | `bool`  | `True`                        | Required for launch                                         |
| `hidden`       | `bool`  | `False`                       | Hidden from TUI display                                     |
| `remap_dict`   | `dict`  | `None`                        | Custom remap; auto-generated if `None`                      |
| `sensor_source`| `str`   | `"mujoco_e3_desktop"`         | Sensor node name for auto-remap topic prefix                |

## `default_data_record_test_node`

| Parameter        | Type    | Default                      | Description                                              |
| ---------------- | ------- | ---------------------------- | -------------------------------------------------------- |
| `name`           | `str`   | `"data_record_test"`         | Node name                                                |
| `rate_hz`        | `float` | `10.0`                       | Control command publish rate (Hz)                        |
| `arm_ctrl_mode`  | `str`   | `"pos"`                      | Arm control mode (`"mit"`, `"pos"`)                       |
| `grip_ctrl_mode` | `str`   | `"pos"`                      | Gripper control mode (`"mit"`, `"pos"`)                   |
| `required`       | `bool`  | `False`                      | Required for launch                                      |
| `hidden`         | `bool`  | `False`                      | Hidden from TUI display                                  |
| `remap_dict`     | `dict`  | `None`                       | Custom remap; auto-generated if `None`                   |
| `sensor_source`  | `str`   | `"mujoco_e3_desktop"`        | Sensor node name for auto-remap of control topics        |
| `keys_source`    | `str`   | `"teleop_keyboard"`          | Keyboard node name for auto-remap of keys topic          |

## `default_data_replay_node`

| Parameter         | Type     | Default                                  | Description                                  |
| ----------------- | -------- | ---------------------------------------- | -------------------------------------------- |
| `name`            | `str`    | `"data_replay"`                          | Node name                                    |
| `mcap_path`       | `str`    | `"~/hex_record_data"`                    | Path to MCAP file or directory               |
| `enable_color`    | `bool`   | `False`                                  | Enable color image replay                    |
| `enable_depth`    | `bool`   | `False`                                  | Enable depth image replay                    |
| `color_encoding`  | `str`    | `"bgr8"`                                 | Color image encoding                         |
| `depth_encoding`  | `str`    | `"mono16"`                               | Depth image encoding                         |
| `rate_hz`         | `float`  | `500.0`                                  | Replay publish rate (Hz)                     |
| `state_topics`    | `list`   | `["left_arm_state", "right_arm_state", "left_grip_state", "right_grip_state", "obj_pose"]` | State topic names to replay |
| `color_topics`    | `list`   | `["head_color"]`                         | Color image topic names to replay            |
| `depth_topics`    | `list`   | `["head_depth"]`                         | Depth image topic names to replay            |
| `required`        | `bool`   | `True`                                   | Required for launch                          |
| `hidden`          | `bool`   | `False`                                  | Hidden from TUI display                      |
| `remap_dict`      | `dict`   | `None`                                   | Custom remap; auto-generated if `None`       |

# 💡 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`:

### Simulation-based recording (E3 Desktop)

```bash
# Records robot state and camera data from a simulated E3 Desktop
# Toggle recording with the S key (keyboard teleoperation)
hexflow run example/sim_record_test.launch.py
```

### Real-robot recording (Dual Archer Y6)

```bash
# Records robot state and camera data from real Archer Y6 arms
# with Berxel depth camera and USB cameras
hexflow run example/real_record_test.launch.py
```

# YAML Examples

### Simulated recording (E3 Desktop)

```yaml
nodes:
  - name: data_record
    build: pip install hex_flow_node_data
    run: hex-flow-data-record
    required: false
    hidden: true
    remap:
      left_arm_state: mujoco_e3_desktop/left_arm_state
      right_arm_state: mujoco_e3_desktop/right_arm_state
      left_grip_state: mujoco_e3_desktop/left_grip_state
      right_grip_state: mujoco_e3_desktop/right_grip_state
      head_color: mujoco_e3_desktop/head_color
      left_color: mujoco_e3_desktop/left_color
      right_color: mujoco_e3_desktop/right_color
      head_depth: mujoco_e3_desktop/head_depth
      left_depth: mujoco_e3_desktop/left_depth
      right_depth: mujoco_e3_desktop/right_depth
      record: data_record_test/record
    env:
      RECORD_PATH: "./record_data"
      FOXGLOVE_HOST: "127.0.0.1"
      FOXGLOVE_PORT: "8765"
      START_CNT: "0"

  - name: data_record_test
    build: pip install hex_flow_node_data
    run: hex-flow-data-record-test
    required: true
    hidden: true
    remap:
      left_arm_ctrl: mujoco_e3_desktop/left_arm_ctrl
      right_arm_ctrl: mujoco_e3_desktop/right_arm_ctrl
      left_grip_ctrl: mujoco_e3_desktop/left_grip_ctrl
      right_grip_ctrl: mujoco_e3_desktop/right_grip_ctrl
      record: data_record_test/record
      keys: teleop_keyboard/teleop_keyboard
    env:
      RATE_HZ: "100"
      ARM_CTRL_MODE: "pos"
      GRIP_CTRL_MODE: "pos"
```

### Real-robot recording (Dual Archer Y6)

```yaml
nodes:
  - name: data_record
    build: pip install hex_flow_node_data
    run: hex-flow-data-record
    required: false
    hidden: true
    remap:
      left_arm_state: left_archer_y6/arm_state
      right_arm_state: right_archer_y6/arm_state
      left_grip_state: left_archer_y6/grip_state
      right_grip_state: right_archer_y6/grip_state
      head_color: head_berxel/color
      head_depth: head_berxel/depth
      left_color: left_cam_usb/color
      record: data_record_test/record
    env:
      RECORD_PATH: "./record_data"
      FOXGLOVE_HOST: "127.0.0.1"
      FOXGLOVE_PORT: "8765"
      START_CNT: "0"

  - name: data_record_test
    build: pip install hex_flow_node_data
    run: hex-flow-data-record-test
    required: true
    hidden: true
    remap:
      left_arm_ctrl: left_archer_y6/arm_ctrl
      right_arm_ctrl: right_archer_y6/arm_ctrl
      left_grip_ctrl: left_archer_y6/grip_ctrl
      right_grip_ctrl: right_archer_y6/grip_ctrl
      record: data_record_test/record
      keys: teleop_keyboard/teleop_keyboard
    env:
      RATE_HZ: "100"
      ARM_CTRL_MODE: "pos"
      GRIP_CTRL_MODE: "pos"
```

# Topics

## Record Node — Subscribed Topics

The record node discovers topics dynamically from the `HEX_FLOW_REMAP` JSON env var, classifying by suffix:

| Suffix       | Message Type                       | Description                        |
| ------------ | ---------------------------------- | ---------------------------------- |
| `*state`     | `HexArmState` / `HexGripState` / `HexPoseState` | Robot joint/pose state             |
| `*color`     | `HexImgSimple` (encoding `bgr8`)   | Color camera frames                |
| `*depth`     | `HexImgSimple` (encoding `mono16`) | Depth camera frames                |
| `record`     | raw bytes (1-byte boolean)         | Start/stop recording (non-zero = start) |

### State topics — `HexArmState`

| Field      | Type        | Description                              |
| ---------- | ----------- | ---------------------------------------- |
| `ts_ns`    | `int64`     | Timestamp in nanoseconds                 |
| `jnt_pos`  | `[float64]` | Joint position array                     |
| `jnt_vel`  | `[float64]` | Joint velocity array                     |
| `jnt_eff`  | `[float64]` | Joint effort/torque array                |
| `pose_pos` | `[float64]` | End-effector position (x, y, z)          |
| `pose_quat`| `[float64]` | End-effector orientation (w, x, y, z)    |

Schema: [`msgs/msg_robot/arm_state.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/msgs/msg_robot/arm_state.fbs)

### State topics — `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)

### State topics — `HexPoseState`

| Field      | Type        | Description                              |
| ---------- | ----------- | ---------------------------------------- |
| `ts_ns`    | `int64`     | Timestamp in nanoseconds                 |
| `pose_pos` | `[float64]` | Object position (x, y, z)                |
| `pose_quat`| `[float64]` | Object orientation (w, x, y, z)          |

Schema: [`msgs/msg_robot/pose_state.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/msgs/msg_robot/pose_state.fbs)

### Camera topics — `HexImgSimple`

| Field      | Type            | Description                                |
| ---------- | --------------- | ------------------------------------------ |
| `ts_ns`    | `int64`         | Timestamp in nanoseconds                   |
| `encoding` | `HexImgEncoding`| Image encoding (`bgr8` or `mono16`)        |
| `width`    | `uint16`        | Image width                                |
| `height`   | `uint16`        | Image height                               |
| `data`     | `[uint8]`       | Raw image pixel data (flattened)           |

Schema: [`msgs/msg_image/img_simple.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/msgs/msg_image/img_simple.fbs)

## Record Test Node — Published Topics

| Topic           | Message Type                 | Description                        |
| --------------- | ---------------------------- | ---------------------------------- |
| `{side}_arm_ctrl` | `HexArmCtrl`              | Arm control command                |
| `{side}_grip_ctrl`| `HexGripCtrl`             | Gripper control command            |
| `record`        | raw bytes (1-byte boolean)   | Start/stop recording command       |

### `{side}_arm_ctrl` — `HexArmCtrl`

| Field      | Type            | Description                                    |
| ---------- | --------------- | ---------------------------------------------- |
| `ts_ns`    | `int64`         | Timestamp in nanoseconds                       |
| `ctrl_mode`| `HexArmCtrlMode`| Control mode enum (`mit`=0, `pos`=2)           |
| `jnt_pos`  | `[float64]`     | Joint position target                          |
| `jnt_vel`  | `[float64]`     | Joint velocity target                          |
| `jnt_eff`  | `[float64]`     | Joint effort target                            |
| `mit_tau`  | `[float64]`     | MIT mode torque feedforward                    |
| `mit_kp`   | `[float64]`     | MIT mode stiffness gains                       |
| `mit_kd`   | `[float64]`     | MIT mode 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)

### `{side}_grip_ctrl` — `HexGripCtrl`

| Field       | Type             | Description                                    |
| ----------- | ---------------- | ---------------------------------------------- |
| `ts_ns`     | `int64`          | Timestamp in nanoseconds                       |
| `ctrl_mode` | `HexGripCtrlMode`| Control mode enum (`mit`=0, `pos`=2)           |
| `jnt_pos`   | `[float64]`      | Gripper joint position target                  |
| `jnt_vel`   | `[float64]`      | Gripper joint velocity target                  |
| `jnt_eff`   | `[float64]`      | Gripper joint effort target                    |
| `mit_tau`   | `[float64]`      | MIT mode torque feedforward                    |
| `mit_kp`    | `[float64]`      | MIT mode stiffness gains                       |
| `mit_kd`    | `[float64]`      | MIT mode 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)

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

## Record Node (`hex-flow-data-record`)

| Variable         | Type     | Default                | Description                              |
| ---------------- | -------- | ---------------------- | ---------------------------------------- |
| `RECORD_PATH`    | `str`    | `"../hex_record_data"` | Directory path for MCAP file output      |
| `FOXGLOVE_HOST`  | `str`    | `"127.0.0.1"`         | Foxglove WebSocket server host           |
| `FOXGLOVE_PORT`  | `int`    | `8765`                 | Foxglove WebSocket server port           |
| `START_CNT`      | `int`    | `0`                    | Starting episode count for file naming   |

## Record Test Node (`hex-flow-data-record-test`)

| Variable          | Type     | Default  | Description                   |
| ----------------- | -------- | -------- | ----------------------------- |
| `RATE_HZ`         | `float`  | `10.0`   | Control command publish rate (Hz) |
| `ARM_CTRL_MODE`   | `str`    | `"pos"`  | Arm control mode (`"mit"`, `"pos"`) |
| `GRIP_CTRL_MODE`  | `str`    | `"pos"`  | Gripper control mode (`"mit"`, `"pos"`) |

## Replay Node (`hex-flow-data-replay`)

| Variable           | Type     | Default    | Description                             |
| ------------------ | -------- | ---------- | --------------------------------------- |
| `MCAP_PATH`        | `str`    | `"~/hex_record_data"` | Path to MCAP file or directory |
| `ENABLE_COLOR`     | `bool`   | `false`    | Enable color image replay               |
| `ENABLE_DEPTH`     | `bool`   | `false`    | Enable depth image replay               |
| `COLOR_ENCODING`   | `str`    | `"bgr8"`   | Color image encoding                    |
| `DEPTH_ENCODING`   | `str`    | `"mono16"` | Depth image encoding                    |
| `RATE_HZ`          | `float`  | `500.0`    | Replay publish rate (Hz)                |

# Architecture

The data recording pipeline follows a modular, decoupled design:

1. **Dynamic topic discovery** — The record node reads the `HEX_FLOW_REMAP` JSON environment variable at startup and classifies all remapped topics by their suffix: `*state` topics (arm/gripper/pose states), `*color` topics (color camera images), and `*depth` topics (depth camera images). This allows the same binary to work with any robot configuration.

2. **Type-specific callbacks** — For each discovered topic, the node creates a dedicated callback that subscribes via `hex_flow_core.Node.create_sub()`. State topics are parsed using `parse_hex_arm_state`, `parse_hex_grip_state`, or `parse_hex_pose_state`; image topics use `parse_color_msg` and `parse_depth_msg`. All parsed data is forwarded to a `HexMcapWriter` instance connected to a Foxglove WebSocket server.

3. **Episode-based recording** — The node listens on the `record` topic for a single-byte boolean payload. On a rising edge (non-zero byte), it starts a new recording episode — incrementing the episode counter and creating a new `episode_NNNNNN.mcap` file at the configured `RECORD_PATH`. On a falling edge (zero byte), it stops the current episode. This enables seamless episode-level data collection.

4. **MCAP output format** — Data is written to Foxglove MCAP files, a standard robotics binary log format that supports schemas, multiple topics, and efficient streaming. MCAP files can be visualized directly in Foxglove Studio for inspection and analysis.

5. **Decoupled test node** — The `hex-flow-data-record-test` node is an independent process that publishes constant arm and gripper control commands at a configurable rate. It also listens for keyboard events and toggles the `record` topic when the `S` key is pressed (with rising-edge detection to avoid double-toggle). This provides a complete recording pipeline that works with both simulation ([`sim_record_test.launch.py`](example/sim_record_test.launch.py)) and real hardware ([`real_record_test.launch.py`](example/real_record_test.launch.py)).

This decoupling allows the high-frequency control loop to run independently from the recording system, ensuring that data collection does not interfere with real-time control.

# 📄 License

Apache License 2.0. See [LICENSE](LICENSE).

# 🌟 Star History

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

# 👥 Contributors

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