Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions atos/CMakeLists.txt
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a WITH_TRUCK_OBJECT_CONTROL CMake option and appended the module to ENABLED_MODULES when the flag is enabled.
This was needed so the new TruckObjectControl module can be built selectively without changing the default build path for existing environments.

Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ set(WITH_BACK_TO_START ON CACHE BOOL "Enable BackToStart module")
set(WITH_OPEN_SCENARIO_GATEWAY ON CACHE BOOL "Enable OpenScenario Gateway module")
set(WITH_REST_BRIDGE ON CACHE BOOL "Enable RESTBridge module")
set(WITH_MONR_RELAY ON CACHE BOOL "Enable MonrRelay module")
set(WITH_TRUCK_OBJECT_CONTROL OFF CACHE BOOL "Enable TruckObjectControl module")

set(ENABLE_TESTS ON CACHE BOOL "Enable testing on build")

Expand Down Expand Up @@ -106,6 +107,9 @@ endif()
if(WITH_MONR_RELAY)
list(APPEND ENABLED_MODULES MonrRelay)
endif()
if(WITH_TRUCK_OBJECT_CONTROL)
list(APPEND ENABLED_MODULES TruckObjectControl)
endif()


# Add corresponding subprojects
Expand Down
31 changes: 17 additions & 14 deletions atos/common/roschannels/monitorchannel.hpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced tf2::toMsg / tf2::fromMsg helper usage with direct quaternion field assignment/construction and added the missing tf2::Matrix3x3 include.
This was done to make the quaternion conversions compile reliably across ROS distros where the tf2_geometry_msgs helper API availability differs.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "atos_interfaces/msg/monitor.hpp"
#include "positioning.h"
#include "roschannel.hpp"
#include <tf2/LinearMath/Matrix3x3.h>
#include <tf2/LinearMath/Quaternion.h>
#if ROS_FOXY
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
Expand Down Expand Up @@ -97,7 +98,10 @@ inline message_type fromISOMonr(const uint32_t id,
if (indata.position.isHeadingValid) {
tf2::Quaternion orientation;
orientation.setRPY(0, 0, indata.position.heading_rad);
outdata.pose.pose.orientation = tf2::toMsg(orientation);
outdata.pose.pose.orientation.x = orientation.x();
outdata.pose.pose.orientation.y = orientation.y();
outdata.pose.pose.orientation.z = orientation.z();
outdata.pose.pose.orientation.w = orientation.w();
}
outdata.velocity.twist.linear.x = indata.speed.isLongitudinalValid ? indata.speed.longitudinal_m_s : 0;
outdata.velocity.twist.linear.y = indata.speed.isLateralValid ? indata.speed.lateral_m_s : 0;
Expand Down Expand Up @@ -128,20 +132,19 @@ inline message_type fromISOMonr(const uint32_t id,

inline ObjectMonitorType toISOMonr(message_type& indata) {
ObjectMonitorType outdata;
outdata.timestamp.tv_sec = indata.atos_header.header.stamp.sec;
outdata.timestamp.tv_usec = indata.atos_header.header.stamp.nanosec / 1000;
outdata.state = static_cast<ObjectStateType>(indata.object_state.state);
outdata.position.isPositionValid = true;
outdata.position.isXcoordValid = true;
outdata.position.isYcoordValid = true;
outdata.position.isZcoordValid = true;
outdata.position.xCoord_m = indata.pose.pose.position.x;
outdata.position.yCoord_m = indata.pose.pose.position.y;
outdata.position.zCoord_m = indata.pose.pose.position.z;
outdata.position.isHeadingValid = true;
tf2::Quaternion quat_tf;
outdata.timestamp.tv_sec = indata.atos_header.header.stamp.sec;
outdata.timestamp.tv_usec = indata.atos_header.header.stamp.nanosec / 1000;
outdata.state = static_cast<ObjectStateType>(indata.object_state.state);
outdata.position.isPositionValid = true;
outdata.position.isXcoordValid = true;
outdata.position.isYcoordValid = true;
outdata.position.isZcoordValid = true;
outdata.position.xCoord_m = indata.pose.pose.position.x;
outdata.position.yCoord_m = indata.pose.pose.position.y;
outdata.position.zCoord_m = indata.pose.pose.position.z;
outdata.position.isHeadingValid = true;
geometry_msgs::msg::Quaternion quat_msg = indata.pose.pose.orientation;
tf2::fromMsg(quat_msg, quat_tf);
tf2::Quaternion quat_tf(quat_msg.x, quat_msg.y, quat_msg.z, quat_msg.w);
double r{}, p{}, y{};
tf2::Matrix3x3 m(quat_tf);
m.getRPY(r, p, y);
Expand Down
14 changes: 11 additions & 3 deletions atos/common/trajectory.cpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added explicit tf2 linear math headers and replaced message-conversion helpers with direct quaternion component copies.
Updated Path header stamping so the path header and pose timestamps share the same time base.
These changes were needed to restore compilation under newer ROS environments and keep emitted path timestamps internally consistent.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <fstream>
#include <iomanip>
#include <iostream>
#include <tf2/LinearMath/Matrix3x3.h>
#include <tf2/LinearMath/Quaternion.h>

#if ROS_FOXY
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
Expand Down Expand Up @@ -62,7 +64,10 @@ atos_interfaces::msg::CartesianTrajectory Trajectory::toCartesianTrajectory() {
// Rotation
tf2::Quaternion q;
q.setRPY(0, 0, point.getHeading());
tf2::convert(q, pointMsg.pose.orientation);
pointMsg.pose.orientation.x = q.x();
pointMsg.pose.orientation.y = q.y();
pointMsg.pose.orientation.z = q.z();
pointMsg.pose.orientation.w = q.w();

// Velocity TODO convert longitudinal / lateral into xyz coordinate system
pointMsg.twist.linear.x = point.getLongitudinalVelocity();
Expand All @@ -82,8 +87,8 @@ atos_interfaces::msg::CartesianTrajectory Trajectory::toCartesianTrajectory() {
nav_msgs::msg::Path Trajectory::toPath() const {
nav_msgs::msg::Path path;
path.header.frame_id = "map";
path.header.stamp = rclcpp::Time(0);
auto rosTimeOffset = rclcpp::Time(std::chrono::system_clock::now().time_since_epoch().count());
path.header.stamp = rosTimeOffset;
for (const auto& point : this->points) {
geometry_msgs::msg::PoseStamped pose;
pose.header.stamp = rosTimeOffset + rclcpp::Duration(point.getTime());
Expand All @@ -92,7 +97,10 @@ nav_msgs::msg::Path Trajectory::toPath() const {
pose.pose.position.z = point.getPosition().z();
tf2::Quaternion q;
q.setRPY(0, 0, point.getHeading());
tf2::convert(q, pose.pose.orientation);
pose.pose.orientation.x = q.x();
pose.pose.orientation.y = q.y();
pose.pose.orientation.z = q.z();
pose.pose.orientation.w = q.w();
path.poses.push_back(pose);
}
// Force same coordinate frame as header
Expand Down
1 change: 1 addition & 0 deletions atos/common/trajectory.hpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a direct #include <rclcpp/rclcpp.hpp>.
This was needed because the implementation now depends on rclcpp::Time / rclcpp::Duration being declared from the public header path, rather than relying on indirect includes.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <limits>
#include <math.h>
#include <nav_msgs/msg/path.hpp>
#include <rclcpp/rclcpp.hpp>
#include <regex>
#include <vector>

Expand Down
14 changes: 8 additions & 6 deletions atos/common/util.c
Copy link
Copy Markdown
Contributor Author

@sepast sepast May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added missing return statements, corrected debug fprintf calls, fixed a bad string terminator assignment, and corrected mismatched debug format arguments.
These changes were needed to resolve hard compiler errors and non-void fallthrough warnings that blocked the build on stricter toolchains.

Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ static int recursiveMkdir(const char *dir, int mode) {
*p = '/';
}
res = mkdir(tmp, mode);
}
return res;
}

/*!
* \brief deleteFile Deletes the file given in the parameter ::path
Expand Down Expand Up @@ -1069,7 +1070,7 @@ int UtilSetSyncPoint(ObjectPosition * OP, double x, double y, double z, double T
}

}

return OP->SyncIndex;
}

float UtilCalculateTimeToSync(ObjectPosition * OP) {
Expand Down Expand Up @@ -1221,6 +1222,7 @@ int UtilSortSpaceTimeAscending(ObjectPosition * OP) {
}
}
}
return 0;
}


Expand All @@ -1235,7 +1237,7 @@ int UtilFindCurrentTrajectoryPosition(ObjectPosition * OP, int StartIndex, doubl
//OP->BestFoundTrajectoryIndex = 0;
if (debug)
fprintf(stderr, "UtilFindCurrentTrajectoryPosition: StartIndex=%d, CurrentTime=%4.3f, MaxTrajDiff=%4.3f, MaxTimeDiff=%4.3f\n",
OP->OrigoDistance, OP->x, OP->y, OP->SyncIndex);
StartIndex, CurrentTime, MaxTrajDiff, MaxTimeDiff);

Init = 1;
while (i < (OP->TrajectoryPositionCount - 1) && i <= OP->SyncIndex) {
Expand Down Expand Up @@ -1328,7 +1330,7 @@ int UtilFindCurrentTrajectoryPositionNew(ObjectPosition * OP, int StartIndex, do
if (i <= -1)
i = 0;
OP->BestFoundTrajectoryIndex = 0;
fprintf("UtilFindCurrentTrajectoryPositionNew: TrajectoryPositionCount=%d, SyncIndex=%d, OrigoDistance=%4.3f, x=%4.3f, y=%4.3f, SyncIndex=%d\n",
fprintf(stderr, "UtilFindCurrentTrajectoryPositionNew: TrajectoryPositionCount=%d, SyncIndex=%d, OrigoDistance=%4.3f, x=%4.3f, y=%4.3f, SyncIndex=%d\n",
OP->TrajectoryPositionCount, OP->SyncIndex, OP->OrigoDistance, OP->x, OP->y, OP->SyncIndex);

Init = 1;
Expand Down Expand Up @@ -1378,7 +1380,7 @@ int UtilFindCurrentTrajectoryPositionNew(ObjectPosition * OP, int StartIndex, do
//SampledSpaceIndex[j] = i;
//j++ ;
if (debug == 2)
fprintf("Minimum: %d, %3.6f, %3.6f", i, AngleDiff, RDiff);
fprintf(stderr, "Minimum: %d, %3.6f, %3.6f", i, AngleDiff, RDiff);
PrevAngleDiff = AngleDiff;
}

Expand Down Expand Up @@ -1754,7 +1756,7 @@ int UtilGetRowInFile(const char *path, const size_t pathLength,
return -1;
}
if(rowIndex == i){
*(rowBuffer+length) = NULL;
*(rowBuffer + length) = '\0';
return i;
}
}
Expand Down
6 changes: 5 additions & 1 deletion atos/modules/EsminiAdapter/src/esminiadapter.cpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made SE_ReportObjectPos conditional on the ROS build environment: Humble keeps the timestamp argument, other environments use the shorter signature.
This was needed because the installed esmini headers differ between environments, so one fixed call signature could not compile everywhere.

Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,12 @@ void EsminiAdapter::reportObjectPosition(const Monitor::message_type::SharedPtr
auto speed = monr->velocity.twist.linear;

// Reporting to Esmini
int timestamp = 0; // Not really used according to esmini documentation
#if ROS_HUMBLE
float timestamp = 0.0f; // Not really used according to esmini documentation
SE_ReportObjectPos(esminiObjectId, timestamp, pos.x, pos.y, pos.z, yaw, pitch, roll);
#else
SE_ReportObjectPos(esminiObjectId, pos.x, pos.y, pos.z, yaw, pitch, roll);
#endif
SE_ReportObjectSpeed(esminiObjectId, speed.x);
}

Expand Down
5 changes: 4 additions & 1 deletion atos/modules/IntegrationTesting/src/integrationtesting.cpp
Copy link
Copy Markdown
Contributor Author

@sepast sepast May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a null/failed-service guard before reading the object-control-state response.
This was needed to prevent the integration test process from crashing when the service call fails during startup or scenario-loading delays.

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ IntegrationTesting::~IntegrationTesting() {}
*/
int IntegrationTesting::getObjectControlState() {
std::shared_ptr<atos_interfaces::srv::GetObjectControlState::Response> response;
this->callService(1000ms, getObjectControlStateClient, response);
if (!this->callService(1000ms, getObjectControlStateClient, response) || response == nullptr) {
RCLCPP_ERROR(get_logger(), "Failed to call service %s", getObjectControlStateClient->get_service_name());
return -1;
}
return int(response->state);
}

Expand Down
10 changes: 5 additions & 5 deletions atos/modules/ObjectControl/src/objectcontrol.cpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Increased the waits for trajectory, IP, and origin service responses, and extended the overall scenario-loading timeout.
This was needed because CI/container startup is slower than local runs, and the previous timeouts were causing false scenario-load failures during integration testing.

Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ ObjectControl::ObjectControl(std::shared_ptr<rclcpp::executors::MultiThreadedExe

objectsConnectedTimer = create_wall_timer(1000ms, std::bind(&ObjectControl::publishObjectIds, this));
idClient = create_client<atos_interfaces::srv::GetObjectIds>(
ServiceNames::getObjectIds, rmw_qos_profile_services_default, id_client_cb_group_);
ServiceNames::getObjectIds, rmw_qos_profile_services_default, id_client_cb_group_);
originClient = create_client<atos_interfaces::srv::GetTestOrigin>(
ServiceNames::getTestOrigin, rmw_qos_profile_services_default, origin_client_cb_group_);
trajectoryClient = create_client<atos_interfaces::srv::GetObjectTrajectory>(
Expand Down Expand Up @@ -295,17 +295,17 @@ bool ObjectControl::loadScenario() {
std::future<bool> trajLoadedFuture = trajLoaded.get_future();
std::future<bool> ipLoadedFuture = ipLoaded.get_future();
std::future<bool> originLoadedFuture = originLoaded.get_future();
if (auto status = trajLoadedFuture.wait_for(5s);
if (auto status = trajLoadedFuture.wait_for(20s);
status != std::future_status::ready || !trajLoadedFuture.get()) {
RCLCPP_ERROR(get_logger(), "Trajectory loading failed for object ID %u", id);
successful = false;
}
if (auto status = ipLoadedFuture.wait_for(250ms);
if (auto status = ipLoadedFuture.wait_for(2s);
status != std::future_status::ready || !ipLoadedFuture.get()) {
RCLCPP_ERROR(get_logger(), "IP loading failed for object ID %u", id);
successful = false;
}
if (auto status = originLoadedFuture.wait_for(250ms);
if (auto status = originLoadedFuture.wait_for(2s);
status != std::future_status::ready || !originLoadedFuture.get()) {
RCLCPP_ERROR(get_logger(), "Origin loading failed for object ID %u", id);
successful = false;
Expand All @@ -326,7 +326,7 @@ bool ObjectControl::loadScenario() {
auto future = idClient->async_send_request(request, idsCallback);
// Wait for all objects to load
std::future scenarioLoadedFuture = scenarioLoaded.get_future();
if (auto status = scenarioLoadedFuture.wait_for(10s); status == std::future_status::ready) {
if (auto status = scenarioLoadedFuture.wait_for(30s); status == std::future_status::ready) {
return scenarioLoadedFuture.get(); // Get the status value
} else if (status == std::future_status::timeout) {
RCLCPP_ERROR(get_logger(), "Scenario loading timed out");
Expand Down
73 changes: 73 additions & 0 deletions atos/modules/TruckObjectControl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

cmake_minimum_required(VERSION 3.8)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

project(truck_object_control)

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(ament_index_cpp REQUIRED)
find_package(OpenSSL REQUIRED)

include(GNUInstallDirs)

set(COREUTILS_LIBRARY ATOSCoreUtil)

add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/truckobjectcontrol.cpp
)

target_include_directories(${PROJECT_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/inc
${CMAKE_CURRENT_SOURCE_DIR}/../../common
)
target_link_libraries(${PROJECT_NAME}
${COREUTILS_LIBRARY}
OpenSSL::SSL
OpenSSL::Crypto
)

ament_target_dependencies(${PROJECT_NAME}
rclcpp
std_msgs
ament_index_cpp
)

add_executable(atos_truck_simulator
${CMAKE_CURRENT_SOURCE_DIR}/src/main_simulator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/atostrucksimulator.cpp
)
target_include_directories(atos_truck_simulator PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/inc
${CMAKE_CURRENT_SOURCE_DIR}/../../common
)
target_link_libraries(atos_truck_simulator
${COREUTILS_LIBRARY}
)
ament_target_dependencies(atos_truck_simulator
rclcpp
std_msgs
ament_index_cpp
)

install(CODE "MESSAGE(STATUS \"Installing target ${PROJECT_NAME}\")")
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION "${CMAKE_INSTALL_LIBDIR}/atos"
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(CODE "MESSAGE(STATUS \"Installing target atos_truck_simulator\")")
install(TARGETS atos_truck_simulator
RUNTIME DESTINATION "${CMAKE_INSTALL_LIBDIR}/atos"
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
59 changes: 59 additions & 0 deletions atos/modules/TruckObjectControl/inc/atostrucksimulator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

#include <string>
#include <vector>

class AtosTruckSimulator : public rclcpp::Node {
public:
AtosTruckSimulator();

private:
struct GeoPoint {
double lat = 0.0;
double lon = 0.0;
double distance_m = 0.0;
};

std::string uid_ = "L5S-TRUCK-SIM";
std::string tcp_host_ = "127.0.0.1";
int tcp_port_ = 8114;
std::string trajectory_geojson_path_ = "";
std::string trajectory_path_name_ = "";
int start_index_ = 0;
double initial_speed_kmh_ = 0.0;
double target_speed_kmh_ = 40.0;
double acceleration_mps2_ = 2.0;
double publish_hz_ = 5.0;
bool loop_path_ = true;
bool ignore_warning_speed_commands_ = false;

double current_distance_m_ = 0.0;
double current_speed_mps_ = 0.0;

int tcp_fd_ = -1;
std::string tcp_rx_buffer_;
std::vector<GeoPoint> trajectory_path_;

rclcpp::TimerBase::SharedPtr simulation_timer_;
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr speed_command_sub_;

rclcpp::Time last_step_time_;

bool loadTrajectoryPath();
bool ensureTcpConnected();
void closeTcp();
void pollTcpCommands();
void simulationStep();
bool pointAtDistance(double distance_m, double& lat, double& lon, double& course_deg, int& path_index) const;
static std::string utcIso8601FromRosTime(const rclcpp::Time& time);
void applySpeedCommandPayload(const std::string& payload);
void onSpeedCommand(const std_msgs::msg::String::SharedPtr msg);
};
Loading
Loading