Testing WMX R2#
Once WMX R2 is installed and built, you can validate it in two ways: in simulation, with no physical hardware, or against real EtherCAT hardware. Start with simulation, then move to real hardware.
Everything on this page uses the general nodes — the five robot-agnostic
nodes in wmx_r2_package. They are the lowest layer of WMX R2: no URDF, no
planner, no robot model. If you can move an axis and read an I/O bit here,
the engine, the EtherCAT bus, and the ROS2 interface are all working.
Every command on this page runs inside the wmx_r2_container Docker
container, through the wros helper — it runs a command in the container
as root, with ROS 2 and the workspace already sourced, and opens an
interactive shell when called with no arguments. Start the container first;
see Install WMX R2 Package.
1. Set the operation mode#
The WMX R2 nodes talk to the WMX engine, which talks to EtherCAT — there is
no built-in mock hardware mode in ROS. The mode is selected in
/opt/wmx3/Module.ini by enabling either the simulation platform or the
EtherCAT platform.
Note
Module.ini is part of the WMX Runtime, which lives on the host at
/opt/wmx3/ and is bind-mounted into the container. Edit it on the host
and restart the nodes; there is no need to rebuild the image.
Disable the EtherCAT platform and enable the simulation platform:
[Platform 0]
Location = ./platform/ethercat
DllName = ec_platform.so
NumOfMaster = 1
disable = 1
[Platform 1]
Location = ./platform/simu
DllName = simu_platform.so
NumOfMaster = 1
disable = 0
This covers any EtherCAT hardware — a robot, a standalone servo drive, an I/O module.
Prerequisites
The WMX R2 workspace is built and sourced
The WMX Runtime is installed at
/opt/wmx3/An EtherCAT cable runs from the compute platform to the first device
The hardware and all servo drives are powered on
You have
sudoprivileges
Wiring
┌──────────────┐ Ethernet ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Compute PC │────(EtherCAT)──►│ Servo J1 │───►│ Servo J2 │── ... ──│ Servo J6 │
│ (dedicated │ └──────────┘ └──────────┘ └──────────┘
│ NIC port) │
└──────────────┘
Use a dedicated Ethernet port for EtherCAT
Servo drives are daisy-chained; each drive has an IN and an OUT port
The I/O module for gripper control sits on the same chain
Enable the EtherCAT platform and disable the simulation platform:
[Platform 0]
Location = ./platform/ethercat
DllName = ec_platform.so
NumOfMaster = 1
disable = 0
[Platform 1]
Location = ./platform/simu
DllName = simu_platform.so
NumOfMaster = 1
disable = 1
Warning
Real Hardware mode moves a physical machine.
Start with the Simulation platform. Before switching to EtherCAT and enabling servos on a robot, complete Try Your Robot — parameter validation, the low-speed single-axis procedure, and the separate safety measures described in Safety Functions and Responsibility.
2. Launch the general nodes#
The launch file starts five nodes: wmx_engine_node,
wmx_lifecycle_manager_node, wmx_core_motion_node, wmx_io_node,
and wmx_ethercat_node.
Launch them inside the WMX R2 container:
wros ros2 launch wmx_r2_package wmx_r2_general_nodes.launch.py \
use_sim_time:=false \
'config_file:=$(ros2 pkg prefix --share wmx_r2_package)/config/wmx_r2_general_nodes_config.yaml' \
'wmx_param_file:=$(ros2 pkg prefix --share wmx_r2_package)/config/wmx_parameters.xml'
What happens at startup#
sequenceDiagram
autonumber
participant L as launch
participant E as wmx_engine_node
participant M as wmx_lifecycle_manager_node
participant N as wmx_core_motion_node<br/>wmx_io_node<br/>wmx_ethercat_node
participant W as WMX3 engine
L->>E: start (ordinary node)
L->>M: start (ordinary node)
L->>N: start (lifecycle, unconfigured)
E->>W: CreateDevice, retry up to 5x on lock error 297
E->>W: StartCommunication
W-->>E: Communicating
loop every discovery_period
M->>E: get_engine_status
end
E-->>M: Communicating
M->>N: configure
M->>N: activate
Note over N: services and topics appear only now
From launch to a node that answers#
Device creation —
wmx_engine_nodecreates the WMX device handle, retrying up to five times if another application holds the lock (error 297).Communication start —
StartCommunicationbrings up the real-time EtherCAT cycle, which discovers the drives on the bus. The engine node does this itself at startup; you do not have to call a service.Bring-up —
wmx_lifecycle_manager_nodepolls the engine status. Once it readsCommunicating, it drives the three lifecycle nodes throughconfigureandactivate, in the order given bymanaged_nodes.
Important
A lifecycle node advertises nothing until it is active. If
ros2 service list shows no /wmx/axes/*, the node has not been
brought up — that is not a crash. Check the states first.
3. Typical startup sequence#
The whole path from freshly launched nodes to a first commanded move and back to a safe stop. Run it once end to end to confirm the stack works; each step is explained in sections 4 to 7.
These examples command axis 0 only. Extend the axis array to your axis
count — axis: [0,1,2,3,4,5] for a six-axis arm — keeping axis and
data the same length.
Warning
Steps 7 and 8 rotate the axis.
In Real Hardware mode, do this at low velocity on an axis that is free to move, with a hand on the emergency stop, after completing First Motion on Hardware.
# 1. Check that every node came up.
wros ros2 service call /wmx/lifecycle/get_node_states \
wmx_r2_message/srv/GetNodeStates "{}"
# 2. Check the engine status. The launch already started the EtherCAT
# cycle, so this should report "Communicating".
wros ros2 service call /wmx/engine/get_engine_status std_srvs/srv/Trigger "{}"
# 3. Set the gear ratio. It defines the user unit of every command below.
# 8388608 counts (23-bit encoder) per 360 degrees makes one command
# unit equal one degree.
wros ros2 service call /wmx/axes/set_gear_ratio wmx_r2_message/srv/SetAxesGearRatio \
"{axis: [0], numerator: [8388608.0], denominator: [360.0]}"
# 4. Clear any amp alarms.
wros ros2 service call /wmx/axes/clear_amp_alarm wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
# 5. Enable the servo.
wros ros2 service call /wmx/axes/set_servo_on wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [1]}"
# 6. Home the axis. With the shipped parameter file the current encoder
# position becomes zero.
wros ros2 service call /wmx/axes/start_home wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
# 7. First move: +10 degrees relative, at low velocity.
wros ros2 service call /wmx/axes/start_mov wmx_r2_message/srv/StartAxesPose \
"{axis: [0], target: [10], velocity: [30], acc: [100], dec: [100]}"
# 8. Or jog by hand instead of step 7. Ctrl+C releases it.
# (see section 7 for the loop)
# 9. Stop the axis.
wros ros2 service call /wmx/axes/stop wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
# 10. Turn the servo off. Always do this before stopping communication.
wros ros2 service call /wmx/axes/set_servo_on wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
Keep a second terminal on the axis feedback while you work through it. After
step 5 servo_on should be true, after step 6 home_done should be
true, and actual_pos should follow step 7:
wros ros2 topic echo /wmx/axes/status
Then Ctrl+C the launch terminal to shut down — see section 10.
4. Verify the nodes are up#
Each node owns one slice of the interface:
Node |
Lifecycle |
Namespace |
Owns |
|---|---|---|---|
|
no |
|
Device handle, EtherCAT communication, engine status, WMX parameter import and inspection |
|
no |
|
Brings every managed node up and down, following the engine |
|
yes |
|
Servo on/off, alarms, command mode, polarity, gear ratio, homing,
stop, motion, and |
|
yes |
|
Digital input and output, by bit and by byte |
|
yes |
|
Master info, ESC register reads, statistics reset, network scan, hot-connect |
wros ros2 node list # expect the five wmx_* nodes
wros ros2 service list | grep /wmx # available WMX services
wros ros2 topic list | grep /wmx # available WMX topics
# engine state: expect "Communicating"
wros ros2 service call /wmx/engine/get_engine_status std_srvs/srv/Trigger "{}"
# every lifecycle node and its state
wros ros2 service call /wmx/lifecycle/get_node_states \
wmx_r2_message/srv/GetNodeStates "{}"
# EtherCAT master and slave status
wros ros2 service call /wmx/ecat/get_master_info \
wmx_r2_message/srv/EcatGetMasterInfo "{master_id: 0}"
If a node is stuck in unconfigured, drive it by hand:
wros ros2 service call /wmx/lifecycle/set_node_state wmx_r2_message/srv/SetNodeState \
"{node_name: 'wmx_core_motion_node', transition: 'bringup'}"
# or everything at once
wros ros2 service call /wmx/lifecycle/set_node_state wmx_r2_message/srv/SetNodeState \
"{node_name: '', transition: 'bringup'}"
wmx_core_motion_node publishes axis feedback at axes_status_rate:
wros ros2 topic echo /wmx/axes/status --once
/wmx/axes/status (wmx_r2_message/msg/AxesStatus) carries one entry per
axis in each of its arrays: amp_alarm, servo_on, home_done,
home_switch, negative_ls, positive_ls, motion_complete,
pos_cmd, velocity_cmd, actual_pos, actual_velocity, and
actual_torque. It is the single best thing to keep echoing in a second
terminal.
The full interface list is in ROS2 Services and ROS2 Topics, and in the general nodes reference.
5. Bring the axes online#
Warning
The general nodes enable no servos.
These nodes are robot-agnostic by design. They do not enable the servos —
that is an explicit /wmx/axes/set_servo_on call — and if you launch
them with an empty wmx_param_file, no axis parameters are imported at
all: the gear ratios, polarities, command modes, and limits in the engine
are whatever a previous session left there.
The per-robot launches (wmx_r2_manipulator.launch.py,
wmx_r2_differential.launch.py) pass their own
example/<robot>_wmx_parameters.xml, and their
joint_state_broadcaster enables the servos when it activates. If you
command motion from the general nodes alone, run the sequence below first.
The sequence is the same in Simulation and Real Hardware mode. The examples
below use six axes; use however many your machine has. The axis and
data arrays must always be the same length.
5-1. Read the active axis parameters
The engine holds whatever axis configuration was last written to it — by a per-robot launch, by WOS, or by a previous session. Read it back before you trust it:
wros ros2 service call /wmx/engine/get_axis_param wmx_r2_message/srv/GetAxisParam \
"{axis: [0,1,2,3,4,5]}"
The reply returns one human-readable dump per axis in axis_param, and the
engine must already be communicating. In the dump, CommandMode is
0=Position, 1=Velocity, 2=Torque; HomeType is
0=CurrentPos, 1=ZPulse, 2=HS, 4=HSZPulse; and
HomeDirection is 0=Positive, 1=Negative.
To load a parameter file into the running engine without restarting:
wros ros2 service call /wmx/engine/import_and_set_all \
wmx_r2_message/srv/ImportAndSetAll \
"{path: '/opt/wmx3/wmx_parameters.xml'}"
The path must be absolute.
5-2. Set the gear ratio
The gear ratio converts encoder counts into the user units every later command uses, so get it right before commanding anything. If a per-robot launch has already applied your robot’s parameter file, the ratio is already correct — confirm it with step 5-1 rather than overwriting it.
For a Panasonic MADLN05BE driver with a 23-bit encoder (8388608 counts per motor revolution), dividing by 360 makes one command unit equal one degree of motor shaft rotation:
wros ros2 service call /wmx/axes/set_gear_ratio wmx_r2_message/srv/SetAxesGearRatio \
"{axis: [0], numerator: [8388608.0], denominator: [360.0]}"
Note
Every target, velocity, acc, and dec value below is in
these user units — degrees with the ratio above, raw encoder counts with a
1:1 ratio. Check the ratio before you trust a number, and remember that a
gearbox between the motor and the joint shifts it again.
5-3. Clear alarms, enable servos, home
# Clear any amp alarms (data is ignored)
wros ros2 service call /wmx/axes/clear_amp_alarm wmx_r2_message/srv/SetAxes \
"{axis: [0,1,2,3,4,5], data: [0,0,0,0,0,0]}"
# Enable the servos (1 = on, 0 = off)
wros ros2 service call /wmx/axes/set_servo_on wmx_r2_message/srv/SetAxes \
"{axis: [0,1,2,3,4,5], data: [1,1,1,1,1,1]}"
# Home all axes (data is ignored)
wros ros2 service call /wmx/axes/start_home wmx_r2_message/srv/SetAxes \
"{axis: [0,1,2,3,4,5], data: [0,0,0,0,0,0]}"
start_home uses the HomeType configured in the WMX parameter file for
that axis. The shipped files use CurrentPos, which makes the current
encoder position zero. The service does not write the home configuration, so
a switch-based or Z-pulse setup is left alone.
Confirm on /wmx/axes/status that amp_alarm is all false,
servo_on all true, and home_done all true before commanding
motion.
Two more axis settings are available when a machine needs them:
# Command mode: 0 = Position, 1 = Velocity, 2 = Torque
wros ros2 service call /wmx/axes/set_axis_command_mode wmx_r2_message/srv/SetAxes \
"{axis: [0,1], data: [0,0]}"
# Polarity: 1 = normal, -1 = reversed
wros ros2 service call /wmx/axes/set_axis_polarity wmx_r2_message/srv/SetAxes \
"{axis: [0,1], data: [1,1]}"
6. Command motion#
Warning
These commands rotate the axes.
On real hardware, start with one axis, a small relative move, and a low velocity, with a hand on the emergency stop. Follow the low-speed single-axis procedure in First Motion on Hardware before doing this on a robot for the first time.
Motion is a service call, not a topic publish. All of them take parallel arrays, so one call can command several axes at once.
Absolute position — move to target in user units:
wros ros2 service call /wmx/axes/start_pos wmx_r2_message/srv/StartAxesPose \
"{axis: [0,1], target: [45, -90], velocity: [10, 20], \
acc: [10, 20], dec: [10, 20]}"
Relative position — move by target from wherever the axis is now.
This is the safer of the two for a first move: a small number stays a small
move regardless of where the axis happens to be.
wros ros2 service call /wmx/axes/start_mov wmx_r2_message/srv/StartAxesPose \
"{axis: [0,1], target: [10, -10], velocity: [10, 20], \
acc: [10, 10], dec: [10, 20]}"
Continuous velocity — the axis keeps turning until you stop it. The sign
of velocity selects the direction.
wros ros2 service call /wmx/axes/start_vel wmx_r2_message/srv/StartAxesVelocity \
"{axis: [0,1], velocity: [10, -10], acc: [10, 20], dec: [10, 20]}"
Stop — decelerate to a standstill. data is ignored:
wros ros2 service call /wmx/axes/stop wmx_r2_message/srv/SetAxes \
"{axis: [0,1], data: [0,0]}"
Watch motion_complete and actual_pos to confirm each move finished
where you expected:
wros ros2 topic echo /wmx/axes/status --field actual_pos
Note
If a motion service answers success: false
While any node listed in motion_controllers is active — the
trajectory, position, and differential controllers — it owns the axes, and
start_pos, start_mov, start_vel, start_jog, and
start_home all refuse. This is deliberate: a manual jog must not fight
a running trajectory.
stop is never blocked. To take manual control, deactivate the
controller:
wros ros2 service call /wmx/lifecycle/set_node_state \
wmx_r2_message/srv/SetNodeState \
"{node_name: 'joint_trajectory_controller', transition: 'deactivate'}"
7. Jog an axis (hold to move)#
/wmx/axes/start_jog is a dead-man command: keep calling it to keep moving,
stop calling it to release. wmx_core_motion_node stops the axis once
refreshes stop arriving (jog_timeout_ms, default 200 ms). The sign of
velocity selects the direction.
Jog runs Motion::StartJog, which requires the axis to be in Position
mode. After the sequence in section 5:
wros ros2 service call /wmx/axes/set_axis_command_mode wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
Then loop the call. Ctrl+C acts as the release:
# positive direction
while true; do
wros ros2 service call /wmx/axes/start_jog wmx_r2_message/srv/StartAxesVelocity \
"{axis: [0], velocity: [10], acc: [100], dec: [100]}"
sleep 0.05
done
# negative direction: negate velocity
while true; do
wros ros2 service call /wmx/axes/start_jog wmx_r2_message/srv/StartAxesVelocity \
"{axis: [0], velocity: [-10], acc: [100], dec: [100]}"
sleep 0.05
done
Stop explicitly at any time:
wros ros2 service call /wmx/axes/stop wmx_r2_message/srv/SetAxes \
"{axis: [0], data: [0]}"
Tuning. These are wmx_core_motion_node parameters, read once at
startup — set them in the config YAML, not with ros2 param set:
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Axis stops this long after refreshes stop arriving |
|
|
Maximum duration of one jog, enforced engine-side |
|
|
Jerk ratio of the jog profile |
Note
jog_run_time_msis enforced by the engine, so the axis still stops if the calling process dies. Once it elapses the axis stays stopped until you release and start again.Repeating the same call only refreshes the dead-man; it does not re-issue
StartJog— jog-over-jog override is undefined in WMX3.accanddecare accelerations on the wire but are converted to ramp times internally, because theTimeAccJerkRatioprofile used here — the same one WOS uses — is time-based.
Tip
For jogging a robot rather than a bare axis, use the keyboard teleop in
movensys-manipulator, which drives the end effector through MoveIt2:
mros ros2 run movensys_manipulator_moveit_config keyboard_teleop
See Trajectory Planning.
8. Read and write digital I/O#
wmx_io_node exposes the EtherCAT I/O image. addr is the I/O byte
address and bit is the index within that byte (0–7). Byte values are
decimal on the wire (15 = 0x0F).
# Read a single input bit / output bit
wros ros2 service call /wmx/io/get_in_bit \
wmx_r2_message/srv/GetIoBit "{addr: 0, bit: 0}"
wros ros2 service call /wmx/io/get_out_bit \
wmx_r2_message/srv/GetIoBit "{addr: 0, bit: 0}"
# Read scattered bits in one call
wros ros2 service call /wmx/io/get_in_bits wmx_r2_message/srv/GetIoBits \
"{addr: [0, 2], bit: [1, 5]}"
# Read a single byte, or a run of bytes
wros ros2 service call /wmx/io/get_in_byte \
wmx_r2_message/srv/GetIoByte "{addr: 0}"
wros ros2 service call /wmx/io/get_in_bytes \
wmx_r2_message/srv/GetIoBytes "{addr: 0, size: 4}"
# Set and clear an output bit (e.g. a gripper solenoid)
wros ros2 service call /wmx/io/set_out_bit \
wmx_r2_message/srv/SetIoBit "{addr: 0, bit: 0, data: 1}"
wros ros2 service call /wmx/io/set_out_bit \
wmx_r2_message/srv/SetIoBit "{addr: 0, bit: 0, data: 0}"
# Write output byte 2 to 0x0F, then bytes 2 and 3 to 0x0F and 0x0E
wros ros2 service call /wmx/io/set_out_byte \
wmx_r2_message/srv/SetIoByte "{addr: 2, data: 15}"
wros ros2 service call /wmx/io/set_out_bytes \
wmx_r2_message/srv/SetIoBytes "{addr: 2, data: [15, 14]}"
A quick way to find which bit a device is wired to: read the input bytes, toggle the device by hand, read them again.
Warning
In Real Hardware mode set_out_bit drives real outputs — grippers,
valves, brakes. Confirm what each output is wired to before writing it.
9. EtherCAT diagnostics#
wmx_ethercat_node is where you look when the bus itself is the suspect —
a slave stuck below Op, packet loss, or a drive that does not appear.
# Master and per-slave state; master 0 unless you run several rings
wros ros2 service call /wmx/ecat/get_master_info \
wmx_r2_message/srv/EcatGetMasterInfo "{master_id: 0}"
The reply gives the master state and mode, the communication period, the
packet-loss, timeout, and over-cycle counters, and parallel arrays describing
every slave — state, AL status code, vendor and product IDs, PDO sizes, axis
count. Master states are None=0, Init=1, Preop=2,
Boot=4, Safeop=8, Op=16; modes are Cyclic=0,
PP=1, Monitor=2. A healthy running bus reports Op for the
master and every slave.
# Read ESC registers directly (reg_addr is decimal, 0x000-0xFFF)
wros ros2 service call /wmx/ecat/register_read wmx_r2_message/srv/EcatRegisterRead \
"{master_id: 0, slave_id: 0, reg_addr: 0, len: 1}" # 0x000 type
wros ros2 service call /wmx/ecat/register_read wmx_r2_message/srv/EcatRegisterRead \
"{master_id: 0, slave_id: 0, reg_addr: 16, len: 4}" # 0x010 vendor ID
wros ros2 service call /wmx/ecat/register_read wmx_r2_message/srv/EcatRegisterRead \
"{master_id: 0, slave_id: 1, reg_addr: 256, len: 16}" # 0x100 DL status
# Reset ref-clock and transmit statistics, then re-scan the network
wros ros2 service call /wmx/ecat/reset_statistics \
wmx_r2_message/srv/EcatResetStatistics "{master_id: 0}"
# Re-scan the network for slaves
wros ros2 service call /wmx/ecat/scan_network \
wmx_r2_message/srv/EcatScanNetwork "{master_id: 0}"
# Enable dynamic slave discovery (call once after the network reaches Op)
wros ros2 service call /wmx/ecat/start_hotconnect \
wmx_r2_message/srv/EcatStartHotconnect "{master_id: 0}"
reg_addr + len must not exceed 0x1000 (4096 bytes).
10. Engine and device control#
wmx_engine_node creates the device and starts communication at startup,
so these services are only needed to cycle either one without restarting the
nodes — after changing Module.ini, for instance, or to reproduce a
startup fault.
# Stop / start the real-time EtherCAT cycle
wros ros2 service call /wmx/engine/set_communication \
std_srvs/srv/SetBool "{data: false}"
wros ros2 service call /wmx/engine/set_communication \
std_srvs/srv/SetBool "{data: true}"
# Close / create the WMX3 device
wros ros2 service call /wmx/engine/set_engine std_srvs/srv/SetBool "{data: false}"
wros ros2 service call /wmx/engine/set_engine std_srvs/srv/SetBool "{data: true}"
Warning
Servos must be off before you stop communication. Stopping the cycle under an enabled servo drops the drive on a communication fault.
Note
Closing the device takes the managed nodes down with it: the lifecycle
manager sees the engine leave Communicating and cleans every node back
to unconfigured, because their device handles are dead. They come back
up on their own when the engine returns.
11. Shutdown#
Press Ctrl+C in the launch terminal. The nodes disable the servos, stop
EtherCAT communication, and close the WMX device.
Next steps#
movensys-manipulator Setup — bring up a full manipulator stack
movensys-navigation Setup — bring up the differential-drive base
API Reference — the complete interface reference
Try Your Robot — before moving a physical robot