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
Metadata
Release files for hex-flow-node-robot 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hex_flow_node_robot-0.0.1.tar.gz | 25.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hex_flow_node_robot-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.3 kB
Release files / hex_flow_node_robot-0.0.1.tar.gz
| Download URL | hex_flow_node_robot-0.0.1.tar.gz |
|---|---|
| Size | 25.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a04a50353a7589550e1c698ffc6267e224520c27327cf803c065e432c54efb9e
|
|
BLAKE2b-256 checksum How to use checksums |
b8642492ef5c7e337502f4ec3bb61cf76984c14ffa53618da66983f3deb79733
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|
Release files / hex_flow_node_robot-0.0.1-py3-none-any.whl
| Download URL | hex_flow_node_robot-0.0.1-py3-none-any.whl |
|---|---|
| Size | 22.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c30fe3926720dffe223795a3ccee0f8764312815e5bed8bada520cadcd0d8d11
|
|
BLAKE2b-256 checksum How to use checksums |
bdc0261a1a3ae3b723a216de12550b9648b2ed1b255d006a2b9224bd09f9035f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|