Skip to content

Commit 6b60455

Browse files
authored
Add Robot Hardware Timestamp Synchronization (#12)
* feat: add robot hardware timestamp synchronization with latency delay parameter * docs: document use_robot_timestamp and t_delay parameters
1 parent e1b0f21 commit 6b60455

5 files changed

Lines changed: 139 additions & 5 deletions

File tree

README.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ The node is started using the provided launch file:
2020
ros2 launch ur_rtde_publisher rtde_publisher.launch.xml \
2121
robot_ip:=192.168.56.101 \
2222
output_recipe:='["payload", "robot_mode"]' \
23-
rtde_frequency:=125
23+
rtde_frequency:=125 \
24+
use_robot_timestamp:=true \
25+
t_delay:=0.004
2426
2527
See the documentation's `usage <https://docs.universal-robots.com/Universal_Robots_ROS_Documentation/rolling/doc/ur_rtde_ros2_publisher/doc/usage.html>`_ section for more details on how to run the node and verify its operation.
2628

@@ -31,6 +33,8 @@ Parameters
3133
* ``output_recipe`` (string[]): List of RTDE output variables to request and publish.
3234
* ``rtde_frequency`` (int, optional, default: ``500``): RTDE communication frequency in Hz.
3335
* ``tf_prefix`` (string, optional, default: ``""``): Optional prefix applied to the ``frame_id`` of stamped ROS 2 messages.
36+
* ``use_robot_timestamp`` (bool, optional, default: ``false``): If true, uses the robot's hardware clock.
37+
* ``t_delay`` (double, optional, default: ``0.0``): Network latency compensation in seconds ``[s]`` to align the data timeline with the actual physical measurement time.
3438

3539
Architecture
3640
------------

doc/usage.rst

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Launch the RTDE publisher node using the provided launch file:
1515
ros2 launch ur_rtde_publisher rtde_publisher.launch.xml \
1616
robot_ip:=192.168.56.101 \
1717
output_recipe:='["payload", "robot_mode"]' \
18-
rtde_frequency:=125
18+
rtde_frequency:=125 \
19+
use_robot_timestamp:=true \
20+
t_delay:=0.004
1921
2022
After launching, verify the node is running:
2123

@@ -81,6 +83,26 @@ The following parameters control the node behavior:
8183
*Example:* setting ``tf_prefix:=robot1`` and the default ``frame_id`` of ``base``
8284
results in ``robot1/base``.
8385

86+
- ``use_robot_timestamp`` (bool, optional, default: ``false``)
87+
88+
When enabled, the node uses the robot controller's internal hardware clock to stamp
89+
ROS 2 messages instead of the host PC's local processing time.
90+
91+
For a detailed explanation of the timeline reconstruction, see the
92+
`Timestamp Synchronization`_ section.
93+
94+
*Example:* ``use_robot_timestamp:=true``
95+
96+
- ``t_delay`` (double, optional, default: ``0.0``)
97+
98+
Constant time offset in seconds (s) to compensate for network latency between the robot and the ROS PC.
99+
100+
This parameter is only effective when ``use_robot_timestamp`` is enabled. It shifts the reconstructed
101+
timeline backward, allowing the ROS timestamps to better approximate the exact moment the physical
102+
measurement occurred on the robot hardware. See `Timestamp Synchronization`_ for the mathematical implementation.
103+
104+
*Example:* ``t_delay:=0.004`` to compensate for an estimated 4 ms communication delay.
105+
84106

85107

86108
Configuration
@@ -103,6 +125,76 @@ systematically prepend a prefix to the default ``frame_id`` values defined in
103125
Apart from frame-related metadata, modifying RTDE variables, message types, or mappings
104126
is not recommended and may lead to inconsistent behavior.
105127

128+
Timestamp Synchronization
129+
-----------------------------------
130+
131+
When the ``use_robot_timestamp`` parameter is enabled, the node switches from host-side
132+
timestamping to a reconstructed timeline anchored directly to the robot controller's
133+
internal hardware clock.
134+
135+
.. note::
136+
The timestamp received over RTDE represents the time in seconds since the controller
137+
startup (boot time).
138+
139+
Timeline Reconstruction Mechanism
140+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141+
142+
**1. Anchor Setup (First Packet)**
143+
144+
Upon receiving the first RTDE packet, the node captures the current host ROS time (:math:`t_{\text{host}}`)
145+
and the initial robot hardware timestamp (:math:`T_0`). It applies the network latency compensation factor
146+
``t_delay`` to calculate an adjusted host base time (:math:`t_0`):
147+
148+
.. math::
149+
t_0 = t_{\text{host}} - t_{\text{delay}}
150+
151+
**2. Linear Tracking (Subsequent Packets)**
152+
153+
For all consecutive packets, the node tracks the elapsed hardware time using the incoming robot timestamp (:math:`T`).
154+
The final ROS 2 message timestamp (:math:`t_{\text{msg}}`) is linearly extrapolated as:
155+
156+
.. math::
157+
t_{\text{msg}} = t_0 + (T - T_0)
158+
159+
**Walkthrough Example:** Consider a configuration using ``use_robot_timestamp:=true`` and ``t_delay:=0.004`` (values in seconds):
160+
161+
.. list-table::
162+
:align: center
163+
:widths: 20 15 25 40
164+
:header-rows: 1
165+
166+
* - Scope / Step
167+
- Variable
168+
- Value
169+
- Mathematical Context / Equation
170+
* - **Packet 1** (Initialization)
171+
- :math:`t_{\text{host}}`
172+
- ``1779781131.140454``
173+
- Host PC reception time
174+
* -
175+
- :math:`T_0`
176+
- ``9010.081000``
177+
- Initial robot hardware timestamp
178+
* -
179+
- :math:`t_{\text{delay}}`
180+
- ``0.004``
181+
- Configured latency compensation
182+
* -
183+
- :math:`t_0`
184+
- ``1779781131.136454``
185+
- Adjusted base ROS time: :math:`t_{\text{host}} - t_{\text{delay}}`
186+
* - **Packet 2** (Next Cycle)
187+
- :math:`T`
188+
- ``9010.087000``
189+
- Current robot hardware timestamp
190+
* -
191+
- :math:`dt`
192+
- ``0.006``
193+
- Elapsed hardware time: :math:`T - T_0`
194+
* -
195+
- :math:`t_{\text{msg}}`
196+
- ``1779781131.142454``
197+
- Final ROS message timestamp: :math:`t_0 + dt`
106198

107199
Typical Workflow
108200
----------------
@@ -136,6 +228,5 @@ There are several aspects users should be aware of when using the RTDE ROS2 Publ
136228
the node, at the time RTDE data is received and published. These timestamps
137229
therefore reflect host‑side reception time rather than the exact time at which
138230
the data was produced by the robot controller.
139-
If precise controller‑side timing is required, users may include the RTDE
140-
``timestamp`` variable in the ``output_recipe`` and use it as a reference for
141-
time synchronization or post‑processing.
231+
If precise controller‑side timing is required, users can enable hardware timeline
232+
reconstruction. See the `Timestamp Synchronization`_ section for details.

include/ur_rtde_publisher/rtde_publisher_node.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ class RtdePublisherNode : public rclcpp::Node
116116
/// @brief TF prefix for the frame IDs published.
117117
std::string tf_prefix_;
118118

119+
/// @brief Parameter flag to enable using the robot's hardware timestamp.
120+
bool use_robot_timestamp_ = false;
121+
122+
/// @brief Estimated communication delay (in seconds) subtracted from the initial ROS timestamp.
123+
double t_delay_ = 0.0;
124+
125+
/// @brief Flag to track if the first RTDE package has been received to initialize time synchronization.
126+
bool first_package_received_ = false;
127+
128+
/// @brief Initial ROS time reference, adjusted by the communication delay.
129+
rclcpp::Time t_0_;
130+
131+
/// @brief Initial robot controller timestamp reference since controller startup [s].
132+
double T_0_ = 0.0;
133+
119134
/// @brief Keys actually created and active after validating with the YAML mapping.
120135
std::vector<std::string> effective_keys_;
121136

launch/rtde_publisher.launch.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ POSSIBILITY OF SUCH DAMAGE. -->
3131
<arg name="output_recipe" default="[]"/>
3232
<arg name="rtde_frequency" default="500"/>
3333
<arg name="tf_prefix" default=""/>
34+
<arg name="use_robot_timestamp" default="false"/>
35+
<arg name="t_delay" default="0.0"/>
3436

3537
<node
3638
pkg="ur_rtde_publisher"
@@ -42,5 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. -->
4244
<param name="output_recipe" value="$(var output_recipe)"/>
4345
<param name="rtde_frequency" value="$(var rtde_frequency)"/>
4446
<param name="tf_prefix" value="$(var tf_prefix)"/>
47+
<param name="use_robot_timestamp" value="$(var use_robot_timestamp)"/>
48+
<param name="t_delay" value="$(var t_delay)"/>
4549
</node>
4650
</launch>

src/rtde_publisher_node.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ bool RtdePublisherNode::loadParameters()
6363
robot_ip_ = declare_parameter<std::string>("robot_ip");
6464
rtde_frequency_ = declare_parameter<int>("rtde_frequency", 500);
6565
tf_prefix_ = declare_parameter<std::string>("tf_prefix", "");
66+
use_robot_timestamp_ = declare_parameter<bool>("use_robot_timestamp", false);
67+
t_delay_ = declare_parameter<double>("t_delay", 0.0);
6668

6769
// Verify parameters
6870
if (output_recipe_.empty() || robot_ip_.empty()) {
@@ -182,6 +184,24 @@ void RtdePublisherNode::spinOnce()
182184

183185
rclcpp::Time packet_time = this->now();
184186

187+
if (use_robot_timestamp_) {
188+
double T_n;
189+
190+
if (pkg_->getData<double>("timestamp", T_n)) {
191+
if (!first_package_received_) {
192+
t_0_ = packet_time - rclcpp::Duration::from_seconds(t_delay_);
193+
T_0_ = T_n;
194+
first_package_received_ = true;
195+
196+
RCLCPP_INFO(this->get_logger(),
197+
"Time synchronization initialized using robot timestamp with a delay of %.4f seconds.", t_delay_);
198+
}
199+
200+
double dt = T_n - T_0_;
201+
packet_time = t_0_ + rclcpp::Duration::from_seconds(dt);
202+
}
203+
}
204+
185205
try {
186206
rtde_publisher_->publish(*pkg_, packet_time);
187207

0 commit comments

Comments
 (0)