Skip to content

Commit 02540be

Browse files
authored
Fix traj point time precision (UniversalRobots#482)
Precision of trajectory point duration was using floor-rounding on millisecond precision. Thus, e.g. `0.019998 sec` would be transferred as `19 ms`. This changes the following: - Change time precision for time from milliseconds to microseconds. This effectively makes the longest duration for a trajectory segment ~36 minutes, which should not be a problem in real-world applications. If a segment longer than the maximum time is sent, this results in a clear error. - Use nearest integer round instead of floor round if time with more precision than 1 microsecond is given. e.g. 0.0199989 sec will be rounded to 19999 us rather than 19998 us. - Use Micrometer precision for the blend radius. Probably not that much relevant, but it is using the same multiplier constant. Backwards compatibility should be given, as long as users haven't hardcoded the constant's value. The change on the wire protocol is injected by the application through the TrajectoryInterface's constant, so even a custom script code would get the updated constant injected correctly decoding the changed wire data.
1 parent ef3cac1 commit 02540be

4 files changed

Lines changed: 85 additions & 16 deletions

File tree

doc/architecture/trajectory_point_interface.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,9 @@ representations in 21 datafields. The data fields have the following meaning:
6969
where
7070

7171
- ``MULT_JOINTSTATE``: 1000000
72-
- ``MULT_TIME``: 1000
72+
- ``MULT_TIME``: 1000000
73+
74+
.. note::
75+
With ``MULT_TIME`` being 1000000, the maximum duration that can be sent is 2147 seconds, while
76+
precision is cut off at 1 microsecond. (The same applies to the blend radius, respectively being
77+
max 2147 m and 1 μm precision.)

include/ur_client_library/control/trajectory_point_interface.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ std::string trajectoryResultToString(const TrajectoryResult result);
6363
class TrajectoryPointInterface : public ReverseInterface
6464
{
6565
public:
66-
static const int32_t MULT_TIME = 1000;
66+
static const int32_t MULT_TIME = 1000000;
6767
static const int MESSAGE_LENGTH = 21;
6868

6969
TrajectoryPointInterface() = delete;
@@ -148,6 +148,8 @@ class TrajectoryPointInterface : public ReverseInterface
148148
virtual void messageCallback(const socket_t filedescriptor, char* buffer, int nbytesrecv) override;
149149

150150
private:
151+
const double MAX_GOAL_TIME_ = static_cast<double>(std::numeric_limits<int32_t>::max()) / MULT_TIME;
152+
151153
std::list<HandlerFunction<void(TrajectoryResult)>> trajectory_end_callbacks_;
152154
uint32_t next_done_callback_id_ = 0;
153155
};

src/control/trajectory_point_interface.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ bool TrajectoryPointInterface::writeMotionPrimitive(const std::shared_ptr<contro
6969
return false;
7070
}
7171

72+
if (primitive->duration.count() > MAX_GOAL_TIME_)
73+
{
74+
URCL_LOG_ERROR("Motion primitive duration is too long. Maximum allowed duration is %f seconds.", MAX_GOAL_TIME_);
75+
return false;
76+
}
77+
7278
if (client_fd_ == -1)
7379
{
7480
return false;
@@ -215,12 +221,12 @@ bool TrajectoryPointInterface::writeTrajectoryPoint(const vector6d_t* positions,
215221
primitive = std::make_shared<MoveLPrimitive>(
216222
urcl::Pose{ (*positions)[0], (*positions)[1], (*positions)[2], (*positions)[3], (*positions)[4],
217223
(*positions)[5] },
218-
blend_radius, std::chrono::milliseconds(static_cast<int>(goal_time * 1000)), acceleration, velocity);
224+
blend_radius, std::chrono::duration<double>(static_cast<double>(goal_time)), acceleration, velocity);
219225
}
220226
else
221227
{
222228
primitive = std::make_shared<MoveJPrimitive>(*positions, blend_radius,
223-
std::chrono::milliseconds(static_cast<int>(goal_time * 1000)),
229+
std::chrono::duration<double>(static_cast<double>(goal_time)),
224230
acceleration, velocity);
225231
}
226232

@@ -255,7 +261,7 @@ bool TrajectoryPointInterface::writeTrajectorySplinePoint(const vector6d_t* posi
255261
}
256262

257263
return writeMotionPrimitive(std::make_shared<SplinePrimitive>(
258-
*positions, *velocities, target_accelerations, std::chrono::milliseconds(static_cast<int>(goal_time * 1000))));
264+
*positions, *velocities, target_accelerations, std::chrono::duration<double>(static_cast<double>(goal_time))));
259265
}
260266

261267
void TrajectoryPointInterface::connectionCallback(const socket_t filedescriptor)

tests/test_trajectory_point_interface.cpp

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <ur_client_library/comm/tcp_socket.h>
3535
#include <ur_client_library/control/motion_primitives.h>
3636
#include <cmath>
37+
#include <limits>
3738
#include "ur_client_library/exceptions.h"
3839

3940
using namespace urcl;
@@ -227,8 +228,8 @@ class TrajectoryPointInterfaceTest : public ::testing::Test
227228
(double)spl.pos[4] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
228229
(double)spl.pos[5] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
229230
},
230-
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_TIME,
231-
std::chrono::milliseconds(spl.goal_time),
231+
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_JOINTSTATE,
232+
std::chrono::microseconds(spl.goal_time),
232233
(double)spl.acc[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
233234
(double)spl.vel[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE);
234235
}
@@ -241,8 +242,8 @@ class TrajectoryPointInterfaceTest : public ::testing::Test
241242
((double)spl.pos[3]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
242243
((double)spl.pos[4]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
243244
((double)spl.pos[5]) / control::TrajectoryPointInterface::MULT_JOINTSTATE },
244-
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_TIME,
245-
std::chrono::milliseconds(spl.goal_time),
245+
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_JOINTSTATE,
246+
std::chrono::microseconds(spl.goal_time),
246247
(double)spl.acc[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
247248
(double)spl.vel[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE);
248249
}
@@ -255,7 +256,7 @@ class TrajectoryPointInterfaceTest : public ::testing::Test
255256
((double)spl.pos[3]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
256257
((double)spl.pos[4]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
257258
((double)spl.pos[5]) / control::TrajectoryPointInterface::MULT_JOINTSTATE },
258-
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_TIME,
259+
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_JOINTSTATE,
259260
(double)spl.acc[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
260261
(double)spl.vel[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE);
261262
}
@@ -274,7 +275,7 @@ class TrajectoryPointInterfaceTest : public ::testing::Test
274275
((double)spl.pos[3]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
275276
((double)spl.pos[4]) / control::TrajectoryPointInterface::MULT_JOINTSTATE,
276277
((double)spl.pos[5]) / control::TrajectoryPointInterface::MULT_JOINTSTATE },
277-
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_TIME,
278+
(double)spl.blend_radius_or_spline_type / control::TrajectoryPointInterface::MULT_JOINTSTATE,
278279
(double)spl.acc[1] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
279280
(double)spl.acc[0] / control::TrajectoryPointInterface::MULT_JOINTSTATE,
280281
static_cast<int32_t>(round((double)spl.acc[2] / control::TrajectoryPointInterface::MULT_JOINTSTATE)));
@@ -384,7 +385,7 @@ TEST_F(TrajectoryPointInterfaceTest, write_quintic_joint_spline)
384385
EXPECT_EQ(send_acc[5], ((double)received_data.acc[5]) / traj_point_interface_->MULT_JOINTSTATE);
385386

386387
// Goal time
387-
EXPECT_EQ(send_goal_time, ((double)received_data.goal_time / traj_point_interface_->MULT_TIME));
388+
EXPECT_EQ(send_goal_time, ((double)received_data.goal_time / traj_point_interface_->MULT_JOINTSTATE));
388389

389390
// Spline type
390391
EXPECT_EQ(static_cast<int32_t>(control::TrajectorySplineType::SPLINE_QUINTIC),
@@ -428,7 +429,7 @@ TEST_F(TrajectoryPointInterfaceTest, write_cubic_joint_spline)
428429
EXPECT_EQ(send_acc[5], ((double)received_data.acc[5]) / traj_point_interface_->MULT_JOINTSTATE);
429430

430431
// Goal time
431-
EXPECT_EQ(send_goal_time, ((double)received_data.goal_time) / traj_point_interface_->MULT_TIME);
432+
EXPECT_EQ(send_goal_time, ((double)received_data.goal_time) / traj_point_interface_->MULT_JOINTSTATE);
432433

433434
// Spline type
434435
EXPECT_EQ(static_cast<int32_t>(control::TrajectorySplineType::SPLINE_CUBIC),
@@ -479,7 +480,62 @@ TEST_F(TrajectoryPointInterfaceTest, write_goal_time)
479480
traj_point_interface_->writeTrajectoryPoint(&send_positions, send_goal_time, 0, false);
480481
int32_t received_goal_time = client_->getGoalTime();
481482

482-
EXPECT_EQ(send_goal_time, ((float)received_goal_time) / traj_point_interface_->MULT_TIME);
483+
EXPECT_EQ(send_goal_time, ((float)received_goal_time) / traj_point_interface_->MULT_JOINTSTATE);
484+
}
485+
486+
// Wire format: int32 microseconds (MULT_TIME). Duration must reach writeMotionPrimitive as seconds
487+
// in double form so encoding uses round(seconds * MULT_TIME), not trunc(int(ms)).
488+
TEST_F(TrajectoryPointInterfaceTest, write_goal_time_preserves_submillisecond_precision)
489+
{
490+
urcl::vector6d_t send_positions = { 0, 0, 0, 0, 0, 0 };
491+
const float send_goal_time = 0.5006f;
492+
const int32_t expected_encoded = static_cast<int32_t>(
493+
std::round(static_cast<double>(send_goal_time) * static_cast<double>(traj_point_interface_->MULT_TIME)));
494+
495+
traj_point_interface_->writeTrajectoryPoint(&send_positions, send_goal_time, 0, false);
496+
EXPECT_EQ(expected_encoded, client_->getGoalTime());
497+
498+
traj_point_interface_->writeTrajectoryPoint(&send_positions, 1.4f, 1.05f, send_goal_time, 0, false);
499+
EXPECT_EQ(expected_encoded, client_->getGoalTime());
500+
501+
// High-precision duration: must match round(goal_time * MULT_TIME), not trunc(goal_time * MULT_TIME).
502+
const float precise_goal_time = 1.234567f;
503+
const int32_t expected_precise = static_cast<int32_t>(
504+
std::round(static_cast<double>(precise_goal_time) * static_cast<double>(traj_point_interface_->MULT_TIME)));
505+
traj_point_interface_->writeTrajectoryPoint(&send_positions, precise_goal_time, 0, false);
506+
EXPECT_EQ(expected_precise, client_->getGoalTime());
507+
traj_point_interface_->writeTrajectoryPoint(&send_positions, 1.4f, 1.05f, precise_goal_time, 0, false);
508+
EXPECT_EQ(expected_precise, client_->getGoalTime());
509+
510+
urcl::vector6d_t send_vel = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
511+
urcl::vector6d_t send_acc = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
512+
traj_point_interface_->writeTrajectorySplinePoint(&send_positions, &send_vel, &send_acc, send_goal_time);
513+
EXPECT_EQ(expected_encoded, client_->getGoalTime());
514+
traj_point_interface_->writeTrajectorySplinePoint(&send_positions, &send_vel, &send_acc, precise_goal_time);
515+
EXPECT_EQ(expected_precise, client_->getGoalTime());
516+
}
517+
518+
// Segment duration is capped by int32 microseconds on the wire (~35.79 minutes).
519+
TEST_F(TrajectoryPointInterfaceTest, write_rejects_goal_time_above_max_encodable_duration)
520+
{
521+
const double max_goal_time_seconds = static_cast<double>(std::numeric_limits<int32_t>::max()) /
522+
static_cast<double>(urcl::control::TrajectoryPointInterface::MULT_TIME);
523+
const float too_long_goal_time = static_cast<float>(max_goal_time_seconds + 1.0);
524+
const float almost_too_long_goal_time = static_cast<float>(max_goal_time_seconds - 1.0);
525+
526+
urcl::vector6d_t send_positions = { 0, 0, 0, 0, 0, 0 };
527+
EXPECT_FALSE(traj_point_interface_->writeTrajectoryPoint(&send_positions, too_long_goal_time, 0, false));
528+
EXPECT_FALSE(traj_point_interface_->writeTrajectoryPoint(&send_positions, 1.4f, 1.05f, too_long_goal_time, 0, false));
529+
EXPECT_TRUE(traj_point_interface_->writeTrajectoryPoint(&send_positions, almost_too_long_goal_time, 0, false));
530+
EXPECT_TRUE(
531+
traj_point_interface_->writeTrajectoryPoint(&send_positions, 1.4f, 1.05f, almost_too_long_goal_time, 0, false));
532+
533+
urcl::vector6d_t send_vel = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
534+
urcl::vector6d_t send_acc = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
535+
EXPECT_FALSE(
536+
traj_point_interface_->writeTrajectorySplinePoint(&send_positions, &send_vel, &send_acc, too_long_goal_time));
537+
EXPECT_TRUE(traj_point_interface_->writeTrajectorySplinePoint(&send_positions, &send_vel, &send_acc,
538+
almost_too_long_goal_time));
483539
}
484540

485541
TEST_F(TrajectoryPointInterfaceTest, write_acceleration_velocity)
@@ -500,7 +556,7 @@ TEST_F(TrajectoryPointInterfaceTest, write_acceleration_velocity)
500556

501557
EXPECT_EQ(send_move_acceleration, ((float)received_move_acceleration) / traj_point_interface_->MULT_JOINTSTATE);
502558
EXPECT_EQ(send_move_velocity, ((float)received_move_velocity) / traj_point_interface_->MULT_JOINTSTATE);
503-
EXPECT_EQ(send_goal_time, ((float)received_goal_time) / traj_point_interface_->MULT_TIME);
559+
EXPECT_EQ(send_goal_time, ((float)received_goal_time) / traj_point_interface_->MULT_JOINTSTATE);
504560
}
505561

506562
TEST_F(TrajectoryPointInterfaceTest, write_blend_radius)
@@ -510,7 +566,7 @@ TEST_F(TrajectoryPointInterfaceTest, write_blend_radius)
510566
traj_point_interface_->writeTrajectoryPoint(&send_positions, 0, send_blend_radius, false);
511567
int32_t received_blend_radius = client_->getBlendRadius();
512568

513-
EXPECT_EQ(send_blend_radius, ((float)received_blend_radius) / traj_point_interface_->MULT_TIME);
569+
EXPECT_EQ(send_blend_radius, ((float)received_blend_radius) / traj_point_interface_->MULT_JOINTSTATE);
514570
}
515571

516572
TEST_F(TrajectoryPointInterfaceTest, write_cartesian)

0 commit comments

Comments
 (0)