From c97d35e16a758220303f7d169ef3246d830ca22c Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 16:23:02 +0900 Subject: [PATCH 01/36] feat(loggers): add a new generic dependency-injectable logger class Signed-off-by: Max SCHMELLER --- .../include/nebula_common/loggers/logger.hpp | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 nebula_common/include/nebula_common/loggers/logger.hpp diff --git a/nebula_common/include/nebula_common/loggers/logger.hpp b/nebula_common/include/nebula_common/loggers/logger.hpp new file mode 100644 index 00000000..b416e252 --- /dev/null +++ b/nebula_common/include/nebula_common/loggers/logger.hpp @@ -0,0 +1,49 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#define NEBULA_LOG_STREAM(log_func, stream_args) \ + { \ + std::stringstream ss{}; \ + ss << stream_args; \ + log_func(ss.str()); \ + } + +namespace nebula::drivers::loggers +{ + +class Logger +{ +public: + Logger() = default; + Logger(const Logger &) = default; + Logger(Logger &&) = delete; + Logger & operator=(const Logger &) = default; + Logger & operator=(Logger &&) = delete; + virtual ~Logger() = default; + + virtual void debug(const std::string & message) = 0; + virtual void info(const std::string & message) = 0; + virtual void warn(const std::string & message) = 0; + virtual void error(const std::string & message) = 0; + + virtual std::shared_ptr child(const std::string & name) = 0; +}; + +} // namespace nebula::drivers::loggers From 4d21fe042dceb0e3f278bfa609f3f5982496d6d2 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 16:23:26 +0900 Subject: [PATCH 02/36] feat(loggers): add a console logger implementation Signed-off-by: Max SCHMELLER --- .../nebula_common/loggers/console_logger.hpp | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 nebula_common/include/nebula_common/loggers/console_logger.hpp diff --git a/nebula_common/include/nebula_common/loggers/console_logger.hpp b/nebula_common/include/nebula_common/loggers/console_logger.hpp new file mode 100644 index 00000000..fb2a6127 --- /dev/null +++ b/nebula_common/include/nebula_common/loggers/console_logger.hpp @@ -0,0 +1,55 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "nebula_common/loggers/logger.hpp" + +#include +#include +#include +#include +#include +#include + +namespace nebula::drivers::loggers +{ + +class ConsoleLogger : public Logger +{ +public: + explicit ConsoleLogger(std::string name) : name_(std::move(name)) {} + + void debug(const std::string & message) override { print_tagged(std::cout, "DEBUG", message); } + void info(const std::string & message) override { print_tagged(std::cout, "INFO", message); } + void warn(const std::string & message) override { print_tagged(std::cerr, "WARN", message); } + void error(const std::string & message) override { print_tagged(std::cerr, "ERROR", message); } + + std::shared_ptr child(const std::string & name) override + { + return std::make_shared(name_ + "." + name); + } + +private: + std::string name_; + + void print_tagged(std::ostream & os, const std::string & severity, const std::string & message) + { + // In multithreaded logging, building the string first (... + ...) and then shifting to the + // stream will ensure that no other logger outputs between string fragments + os << ("[" + name_ + "][" + severity + "] " + message) << std::endl; + } +}; + +} // namespace nebula::drivers::loggers From c6fcf9b29318fc144b6c53810a543eac7fe171d3 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 16:23:46 +0900 Subject: [PATCH 03/36] feat(loggers): add an rclcpp logger implementation Signed-off-by: Max SCHMELLER --- .../nebula_ros/common/rclcpp_logger.hpp | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 nebula_ros/include/nebula_ros/common/rclcpp_logger.hpp diff --git a/nebula_ros/include/nebula_ros/common/rclcpp_logger.hpp b/nebula_ros/include/nebula_ros/common/rclcpp_logger.hpp new file mode 100644 index 00000000..33343528 --- /dev/null +++ b/nebula_ros/include/nebula_ros/common/rclcpp_logger.hpp @@ -0,0 +1,59 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include +#include + +namespace nebula::drivers::loggers +{ + +class RclcppLogger : public Logger +{ +public: + explicit RclcppLogger(const std::string & name) : underlying_logger_(rclcpp::get_logger(name)) {} + explicit RclcppLogger(const rclcpp::Logger & underlying) : underlying_logger_(underlying) {} + + void debug(const std::string & message) override + { + RCLCPP_DEBUG_STREAM(underlying_logger_, message); + } + void info(const std::string & message) override + { + RCLCPP_INFO_STREAM(underlying_logger_, message); + } + void warn(const std::string & message) override + { + RCLCPP_WARN_STREAM(underlying_logger_, message); + } + void error(const std::string & message) override + { + RCLCPP_ERROR_STREAM(underlying_logger_, message); + } + + std::shared_ptr child(const std::string & name) override + { + return std::make_shared(underlying_logger_.get_child(name)); + } + +private: + rclcpp::Logger underlying_logger_; +}; + +} // namespace nebula::drivers::loggers From 3c08c5123d3a213483e66468218fa12a3999ff80 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 16:39:40 +0900 Subject: [PATCH 04/36] chore(hesai_hw_interfaces): switch to the new loggers::Logger class, remove rclcpp dependency :tada: Signed-off-by: Max SCHMELLER --- .../hesai_hw_interface.hpp | 37 ++-- .../hesai_hw_interface.cpp | 165 +++++++----------- nebula_ros/src/hesai/hw_interface_wrapper.cpp | 10 +- 3 files changed, 83 insertions(+), 129 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp index d37a33e0..8820a4f2 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp @@ -17,7 +17,7 @@ // Have to define macros to silence warnings about deprecated headers being used by // boost/property_tree/ in some versions of boost. // See: https://github.com/boostorg/property_tree/issues/51 -#include "nebula_common/nebula_status.hpp" +#include #include @@ -28,15 +28,15 @@ #if (BOOST_VERSION / 100 == 1074) // Boost 1.74 #define BOOST_ALLOW_DEPRECATED_HEADERS #endif -#include "boost_tcp_driver/http_client_driver.hpp" -#include "boost_tcp_driver/tcp_driver.hpp" -#include "boost_udp_driver/udp_driver.hpp" -#include "nebula_common/hesai/hesai_common.hpp" -#include "nebula_common/hesai/hesai_status.hpp" -#include "nebula_common/util/expected.hpp" #include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_cmd_response.hpp" -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -136,6 +136,7 @@ class HesaiHwInterface using ptc_cmd_result_t = nebula::util::expected, ptc_error_t>; + std::shared_ptr logger_; std::unique_ptr<::drivers::common::IoContext> cloud_io_context_; std::shared_ptr m_owned_ctx; std::unique_ptr<::drivers::udp_driver::UdpDriver> cloud_udp_driver_; @@ -165,20 +166,6 @@ class HesaiHwInterface /// @param str Received string void str_cb(const std::string & str); - std::shared_ptr parent_node_logger; - /// @brief Printing the string to RCLCPP_INFO_STREAM - /// @param info Target string - void PrintInfo(std::string info); - /// @brief Printing the string to RCLCPP_ERROR_STREAM - /// @param error Target string - void PrintError(std::string error); - /// @brief Printing the string to RCLCPP_DEBUG_STREAM - /// @param debug Target string - void PrintDebug(std::string debug); - /// @brief Printing the bytes to RCLCPP_DEBUG_STREAM - /// @param bytes Target byte vector - void PrintDebug(const std::vector & bytes); - /// @brief Convert an error code to a human-readable string /// @param error_code The error code, containing the sensor's error code (if any), along with /// flags such as TCP_ERROR_UNRELATED_RESPONSE etc. @@ -202,7 +189,7 @@ class HesaiHwInterface public: /// @brief Constructor - HesaiHwInterface(); + explicit HesaiHwInterface(std::shared_ptr logger); /// @brief Destructor ~HesaiHwInterface(); /// @brief Initializing tcp_driver for TCP communication @@ -460,10 +447,6 @@ class HesaiHwInterface /// @brief Whether to use HTTP for getting LidarMonitor /// @return Use HTTP bool UseHttpGetLidarMonitor(); - - /// @brief Setting rclcpp::Logger - /// @param node Logger - void SetLogger(std::shared_ptr node); }; } // namespace nebula::drivers diff --git a/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp b/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp index 0a08dd93..b3ecc69e 100644 --- a/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp +++ b/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // #define WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE @@ -27,17 +28,21 @@ #include +#include + namespace nebula::drivers { using std::string_literals::operator""s; using nlohmann::json; -HesaiHwInterface::HesaiHwInterface() -: cloud_io_context_{new ::drivers::common::IoContext(1)}, +HesaiHwInterface::HesaiHwInterface(std::shared_ptr logger) +: logger_(std::move(logger)), + cloud_io_context_{new ::drivers::common::IoContext(1)}, m_owned_ctx{new boost::asio::io_context(1)}, cloud_udp_driver_{new ::drivers::udp_driver::UdpDriver(*cloud_io_context_)}, - tcp_driver_{new ::drivers::tcp_driver::TcpDriver(m_owned_ctx)} + tcp_driver_{new ::drivers::tcp_driver::TcpDriver(m_owned_ctx)}, + target_model_no(NebulaModelToHesaiModelNo(SensorModel::UNKNOWN)) { } @@ -76,17 +81,17 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( << " (" << len << ") "; std::string log_tag = ss.str(); - PrintDebug(log_tag + "Entering lock"); + logger_->debug(log_tag + "Entering lock"); std::timed_mutex tm; tm.lock(); if (tcp_driver_->GetIOContext()->stopped()) { - PrintDebug(log_tag + "IOContext was stopped"); + logger_->debug(log_tag + "IOContext was stopped"); tcp_driver_->GetIOContext()->restart(); } - PrintDebug(log_tag + "Sending payload"); + logger_->debug(log_tag + "Sending payload"); tcp_driver_->asyncSendReceiveHeaderPayload( send_buf, [this, log_tag, command_id, response_complete, @@ -95,7 +100,7 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( size_t payload_len = (header_bytes[4] << 24) | (header_bytes[5] << 16) | (header_bytes[6] << 8) | header_bytes[7]; - PrintDebug( + logger_->debug( log_tag + "Received header (expecting " + std::to_string(payload_len) + "B payload)"); // If command_id in the response does not match, we got a response for another command (or // rubbish), probably as a result of too many simultaneous TCP connections to the sensor (e.g. @@ -109,7 +114,7 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( }, [this, log_tag, recv_buf, response_complete, error_code](const std::vector & payload_bytes) { - PrintDebug(log_tag + "Received payload"); + logger_->debug(log_tag + "Received payload"); // Header had payload length 0 (thus, header callback processed request successfully already), // but we still received a payload: invalid state @@ -123,19 +128,19 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( *response_complete = true; }, [this, log_tag, &tm]() { - PrintDebug(log_tag + "Unlocking mutex"); + logger_->debug(log_tag + "Unlocking mutex"); tm.unlock(); - PrintDebug(log_tag + "Unlocked mutex"); + logger_->debug(log_tag + "Unlocked mutex"); }); this->IOContextRun(); if (!tm.try_lock_for(std::chrono::seconds(1))) { - PrintError(log_tag + "Request did not finish within 1s"); + logger_->error(log_tag + "Request did not finish within 1s"); error_code->error_flags |= TCP_ERROR_TIMEOUT; return *error_code; } if (!response_complete) { - PrintError(log_tag + "Did not receive response"); + logger_->error(log_tag + "Did not receive response"); error_code->error_flags |= TCP_ERROR_INCOMPLETE_RESPONSE; return *error_code; } @@ -144,7 +149,7 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( return *error_code; } - PrintDebug(log_tag + "Received response"); + logger_->debug(log_tag + "Received response"); return *recv_buf; } @@ -160,7 +165,7 @@ Status HesaiHwInterface::SetSensorConfiguration( Status HesaiHwInterface::SensorInterfaceStart() { try { - PrintInfo("Starting UDP receiver"); + logger_->info("Starting UDP receiver"); if (sensor_configuration_->multicast_ip.empty()) { cloud_udp_driver_->init_receiver( sensor_configuration_->host_ip, sensor_configuration_->data_port); @@ -171,16 +176,16 @@ Status HesaiHwInterface::SensorInterfaceStart() cloud_udp_driver_->receiver()->setMulticast(true); } #ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - PrintError("init ok"); + logger_->error("init ok"); #endif cloud_udp_driver_->receiver()->open(); #ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - PrintError("open ok"); + logger_->error("open ok"); #endif bool success = cloud_udp_driver_->receiver()->setKernelBufferSize(UDP_SOCKET_BUFFER_SIZE); if (!success) { - PrintError( + logger_->error( "Could not set receive buffer size. Try increasing net.core.rmem_max to " + std::to_string(UDP_SOCKET_BUFFER_SIZE) + " B."); return Status::ERROR_1; @@ -188,13 +193,13 @@ Status HesaiHwInterface::SensorInterfaceStart() cloud_udp_driver_->receiver()->bind(); #ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - PrintError("bind ok"); + logger_->error("bind ok"); #endif cloud_udp_driver_->receiver()->asyncReceive( std::bind(&HesaiHwInterface::ReceiveSensorPacketCallback, this, std::placeholders::_1)); #ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - PrintError("async receive set"); + logger_->error("async receive set"); #endif } catch (const std::exception & ex) { Status status = Status::UDP_CONNECTION_ERROR; @@ -226,14 +231,14 @@ Status HesaiHwInterface::GetSensorConfiguration( { std::stringstream ss; ss << sensor_configuration; - PrintDebug(ss.str()); + logger_->debug(ss.str()); return Status::ERROR_1; } Status HesaiHwInterface::GetCalibrationConfiguration( CalibrationConfigurationBase & calibration_configuration) { - PrintDebug(calibration_configuration.calibration_file); + logger_->debug(calibration_configuration.calibration_file); return Status::ERROR_1; } @@ -270,7 +275,7 @@ Status HesaiHwInterface::FinalizeTcpDriver() tcp_driver_->close(); } } catch (std::exception & e) { - PrintError("Error while finalizing the TcpDriver"); + logger_->error("Error while finalizing the TcpDriver"); return Status::UDP_CONNECTION_ERROR; } return Status::OK; @@ -672,7 +677,7 @@ HesaiPtpConfig HesaiHwInterface::GetPtpConfig() if (response.size() < sizeof(HesaiPtpConfig)) { throw std::runtime_error("HesaiPtpConfig has unexpected payload size"); } else if (response.size() > sizeof(HesaiPtpConfig)) { - PrintError("HesaiPtpConfig from Sensor has unknown format. Will parse anyway."); + logger_->error("HesaiPtpConfig from Sensor has unknown format. Will parse anyway."); } HesaiPtpConfig hesai_ptp_config; @@ -735,7 +740,7 @@ HesaiStatus HesaiHwInterface::GetHttpClientDriverOnce( std::stringstream ss; ss << "HesaiHwInterface::GetHttpClientDriverOnce: " << status << sensor_configuration_->sensor_ip << "," << 80 << std::endl; - PrintError(ss.str()); + logger_->error(ss.str()); return Status::HTTP_CONNECTION_ERROR; } return Status::OK; @@ -752,7 +757,7 @@ HesaiStatus HesaiHwInterface::GetHttpClientDriverOnce( void HesaiHwInterface::str_cb(const std::string & str) { - PrintInfo(str); + logger_->info(str); } std::pair HesaiHwInterface::unwrap_http_response( @@ -881,7 +886,7 @@ HesaiStatus HesaiHwInterface::GetLidarMonitorAsyncHttp( std::unique_ptr<::drivers::tcp_driver::HttpClientDriver> hcd; auto st = GetHttpClientDriverOnce(ctx, hcd); if (st != Status::OK) { - PrintError("HesaiHwInterface::GetLidarMonitorAsyncHttp: cannot GetHttpClientDriverOnce"); + logger_->error("HesaiHwInterface::GetLidarMonitorAsyncHttp: cannot GetHttpClientDriverOnce"); return st; } @@ -891,7 +896,7 @@ HesaiStatus HesaiHwInterface::GetLidarMonitorAsyncHttp( boost::system::error_code ec; ctx->run(ec); if (ec) { - PrintError("HesaiHwInterface::GetLidarMonitorAsyncHttp: " + ec.message()); + logger_->error("HesaiHwInterface::GetLidarMonitorAsyncHttp: " + ec.message()); } return Status::WAITING_FOR_SENSOR_RESPONSE; } @@ -918,15 +923,15 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( if (sensor_configuration->return_mode != current_return_mode) { std::stringstream ss; ss << current_return_mode; - PrintInfo("Current LiDAR return_mode: " + ss.str()); + logger_->info("Current LiDAR return_mode: " + ss.str()); std::stringstream ss2; ss2 << sensor_configuration->return_mode; - PrintInfo("Current Configuration return_mode: " + ss2.str()); + logger_->info("Current Configuration return_mode: " + ss2.str()); std::thread t([this, sensor_configuration] { auto return_mode_int = nebula::drivers::int_from_return_mode_hesai( sensor_configuration->return_mode, sensor_configuration->sensor_model); if (return_mode_int < 0) { - PrintError( + logger_->error( "Invalid Return Mode for this sensor. Please check your settings. Falling back to Dual " "mode."); return_mode_int = 2; @@ -939,16 +944,16 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( auto current_rotation_speed = hesai_config.spin_rate; if (sensor_configuration->rotation_speed != current_rotation_speed.value()) { - PrintInfo( + logger_->info( "current lidar rotation_speed: " + std::to_string(static_cast(current_rotation_speed.value()))); - PrintInfo( + logger_->info( "current configuration rotation_speed: " + std::to_string(sensor_configuration->rotation_speed)); if (UseHttpSetSpinRate()) { SetSpinSpeedAsyncHttp(sensor_configuration->rotation_speed); } else { - PrintInfo( + logger_->info( "Setting up spin rate via TCP." + std::to_string(sensor_configuration->rotation_speed)); std::thread t( [this, sensor_configuration] { SetSpinRate(sensor_configuration->rotation_speed); }); @@ -969,27 +974,27 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( : sensor_configuration->multicast_ip; if (desired_host_addr != current_host_addr) { set_flg = true; - PrintInfo("current lidar dest_ipaddr: " + current_host_addr); - PrintInfo("current configuration host_ip: " + desired_host_addr); + logger_->info("current lidar dest_ipaddr: " + current_host_addr); + logger_->info("current configuration host_ip: " + desired_host_addr); } auto current_host_dport = hesai_config.dest_LiDAR_udp_port; if (sensor_configuration->data_port != current_host_dport.value()) { set_flg = true; - PrintInfo( + logger_->info( "current lidar dest_LiDAR_udp_port: " + std::to_string(static_cast(current_host_dport.value()))); - PrintInfo( + logger_->info( "current configuration data_port: " + std::to_string(sensor_configuration->data_port)); } auto current_host_tport = hesai_config.dest_gps_udp_port; if (sensor_configuration->gnss_port != current_host_tport.value()) { set_flg = true; - PrintInfo( + logger_->info( "current lidar dest_gps_udp_port: " + std::to_string(static_cast(current_host_tport.value()))); - PrintInfo( + logger_->info( "current configuration gnss_port: " + std::to_string(sensor_configuration->gnss_port)); } @@ -1015,9 +1020,9 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( set_flg = true; } if (sync_flg && set_flg) { - PrintInfo("current lidar sync: " + std::to_string(hesai_config.sync)); - PrintInfo("current lidar sync_angle: " + std::to_string(sensor_sync_angle)); - PrintInfo("current configuration sync_angle: " + std::to_string(config_sync_angle)); + logger_->info("current lidar sync: " + std::to_string(hesai_config.sync)); + logger_->info("current lidar sync_angle: " + std::to_string(sensor_sync_angle)); + logger_->info("current configuration sync_angle: " + std::to_string(config_sync_angle)); std::thread t( [this, sync_flg, config_sync_angle] { SetSyncAngle(sync_flg, config_sync_angle); }); t.join(); @@ -1031,7 +1036,7 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( sensor_configuration->sensor_model == SensorModel::HESAI_PANDARQT64 || sensor_configuration->sensor_model == SensorModel::HESAI_PANDARXT32 || sensor_configuration->sensor_model == SensorModel::HESAI_PANDARXT32M) { - PrintInfo("Trying to set Clock source to PTP"); + logger_->info("Trying to set Clock source to PTP"); SetClockSource(HESAI_LIDAR_PTP_CLOCK_SOURCE); } std::ostringstream tmp_ostringstream; @@ -1039,28 +1044,28 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( << ", Domain: " << std::to_string(sensor_configuration->ptp_domain) << ", Transport: " << sensor_configuration->ptp_transport_type << ", Switch Type: " << sensor_configuration->ptp_switch_type << " via TCP"; - PrintInfo(tmp_ostringstream.str()); + logger_->info(tmp_ostringstream.str()); SetPtpConfig( static_cast(sensor_configuration->ptp_profile), sensor_configuration->ptp_domain, static_cast(sensor_configuration->ptp_transport_type), static_cast(sensor_configuration->ptp_switch_type), PTP_LOG_ANNOUNCE_INTERVAL, PTP_SYNC_INTERVAL, PTP_LOG_MIN_DELAY_INTERVAL); - PrintDebug("Setting properties done"); + logger_->debug("Setting properties done"); }); - PrintDebug("Waiting for thread to finish"); + logger_->debug("Waiting for thread to finish"); t.join(); - PrintDebug("Thread finished"); + logger_->debug("Thread finished"); std::this_thread::sleep_for(wait_time); } else { // AT128 only supports PTP setup via HTTP - PrintInfo("Trying to set SyncAngle via HTTP"); + logger_->info("Trying to set SyncAngle via HTTP"); SetSyncAngleSyncHttp(1, sensor_configuration->sync_angle); std::ostringstream tmp_ostringstream; tmp_ostringstream << "Trying to set PTP Config: " << sensor_configuration->ptp_profile << ", Domain: " << sensor_configuration->ptp_domain << ", Transport: " << sensor_configuration->ptp_transport_type << " via HTTP"; - PrintInfo(tmp_ostringstream.str()); + logger_->info(tmp_ostringstream.str()); SetPtpConfigSyncHttp( static_cast(sensor_configuration->ptp_profile), sensor_configuration->ptp_domain, static_cast(sensor_configuration->ptp_transport_type), PTP_LOG_ANNOUNCE_INTERVAL, @@ -1070,7 +1075,7 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( #ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE std::cout << "End CheckAndSetConfig(HesaiConfig)!!" << std::endl; #endif - PrintDebug("GetAndCheckConfig(HesaiConfig) finished"); + logger_->debug("GetAndCheckConfig(HesaiConfig) finished"); return Status::OK; } @@ -1097,10 +1102,10 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( static_cast(sensor_configuration->cloud_min_angle * 10) != current_cloud_min_angle_ddeg.value()) { set_flg = true; - PrintInfo( + logger_->info( "current lidar range.start: " + std::to_string(static_cast(current_cloud_min_angle_ddeg.value()))); - PrintInfo( + logger_->info( "current configuration cloud_min_angle: " + std::to_string(sensor_configuration->cloud_min_angle)); } @@ -1110,10 +1115,10 @@ HesaiStatus HesaiHwInterface::CheckAndSetConfig( static_cast(sensor_configuration->cloud_max_angle * 10) != current_cloud_max_angle_ddeg.value()) { set_flg = true; - PrintInfo( + logger_->info( "current lidar range.end: " + std::to_string(static_cast(current_cloud_max_angle_ddeg.value()))); - PrintInfo( + logger_->info( "current configuration cloud_max_angle: " + std::to_string(sensor_configuration->cloud_max_angle)); } @@ -1307,48 +1312,6 @@ bool HesaiHwInterface::UseHttpGetLidarMonitor() return UseHttpGetLidarMonitor(target_model_no); } -void HesaiHwInterface::SetLogger(std::shared_ptr logger) -{ - parent_node_logger = logger; -} - -void HesaiHwInterface::PrintInfo(std::string info) -{ - if (parent_node_logger) { - RCLCPP_INFO_STREAM((*parent_node_logger), info); - } else { - std::cout << info << std::endl; - } -} - -void HesaiHwInterface::PrintError(std::string error) -{ - if (parent_node_logger) { - RCLCPP_ERROR_STREAM((*parent_node_logger), error); - } else { - std::cerr << error << std::endl; - } -} - -void HesaiHwInterface::PrintDebug(std::string debug) -{ - if (parent_node_logger) { - RCLCPP_DEBUG_STREAM((*parent_node_logger), debug); - } else { - std::cout << debug << std::endl; - } -} - -void HesaiHwInterface::PrintDebug(const std::vector & bytes) -{ - std::stringstream ss; - for (const auto & b : bytes) { - ss << static_cast(b) << ", "; - } - ss << std::endl; - PrintDebug(ss.str()); -} - std::string HesaiHwInterface::PrettyPrintPTCError(ptc_error_t error_code) { if (error_code.ok()) { @@ -1430,9 +1393,13 @@ T HesaiHwInterface::CheckSizeAndParse(const std::vector & data) } if (data.size() > sizeof(T)) { - RCLCPP_WARN_ONCE( - *parent_node_logger, - "Sensor returned longer payload than expected. Truncating and parsing anyway."); + // TODO(mojomex): having a static variable for this is not optimal, but the loggers::Logger + // class does not support things like _ONCE macros yet + static bool already_warned_for_this_type = false; + if (!already_warned_for_this_type) { + logger_->warn("Sensor returned longer payload than expected. Truncating and parsing anyway."); + already_warned_for_this_type = true; + } } T parsed; diff --git a/nebula_ros/src/hesai/hw_interface_wrapper.cpp b/nebula_ros/src/hesai/hw_interface_wrapper.cpp index b5f99109..a89a0a37 100644 --- a/nebula_ros/src/hesai/hw_interface_wrapper.cpp +++ b/nebula_ros/src/hesai/hw_interface_wrapper.cpp @@ -2,7 +2,11 @@ #include "nebula_ros/hesai/hw_interface_wrapper.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp" #include "nebula_ros/common/parameter_descriptors.hpp" +#include "nebula_ros/common/rclcpp_logger.hpp" + +#include namespace nebula::ros { @@ -10,8 +14,9 @@ namespace nebula::ros HesaiHwInterfaceWrapper::HesaiHwInterfaceWrapper( rclcpp::Node * const parent_node, std::shared_ptr & config, bool use_udp_only) -: hw_interface_(new nebula::drivers::HesaiHwInterface()), - logger_(parent_node->get_logger().get_child("HwInterface")), +: hw_interface_(std::make_shared( + drivers::loggers::RclcppLogger(parent_node->get_logger()).child("HwInterface"))), + logger_(parent_node->get_logger().get_child("HwInterfaceWrapper")), status_(Status::NOT_INITIALIZED), use_udp_only_(use_udp_only) { @@ -26,7 +31,6 @@ HesaiHwInterfaceWrapper::HesaiHwInterfaceWrapper( (std::stringstream{} << "Could not initialize HW interface: " << status_).str()); } - hw_interface_->SetLogger(std::make_shared(parent_node->get_logger())); hw_interface_->SetTargetModel(config->sensor_model); if (use_udp_only) { From c59c9bdb38d4dfe83ff9d804c3a8c2470b45e2b4 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 11:47:29 +0900 Subject: [PATCH 05/36] feat(udp): a new UDP socket implementation Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp new file mode 100644 index 00000000..c05b13f5 --- /dev/null +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -0,0 +1,286 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nebula::drivers::connections +{ + +class SocketError : public std::exception +{ +public: + explicit SocketError(int err_no) : what_(strerror(err_no)) {} + + explicit SocketError(const char * msg) : what_(msg) {} + + const char * what() const noexcept override { return what_.c_str(); } + +private: + std::string what_; +}; + +class UdpSocket +{ +private: + struct Endpoint + { + std::string ip; + uint16_t port; + }; + + struct MsgBuffers + { + msghdr msg{}; + iovec iov{}; + std::array control; + sockaddr_in sender_addr; + }; + + enum class State { UNINITIALIZED, INITIALIZED, BOUND, ACTIVE }; + +public: + using callback_t = std::function &, uint64_t)>; + + /** + * @brief Construct a UDP socket with timestamp measuring enabled. The minimal way to start + * receiving on the socket is `UdpSocket().init(...).bind().subscribe(...);`. + */ + UdpSocket() + { + int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd == -1) throw SocketError(errno); + + int enable = 1; + int result = setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0; + if (result < 0) throw SocketError((errno)); + + sock_fd_ = sock_fd; + } + + /** + * @brief Specify the host address and port for this socket to be bound to. To bind the socket, + * call the `bind()` function. + * + * @param host_ip The address to bind to. + * @param host_port The port to bind to. + */ + UdpSocket & init(const std::string & host_ip, uint16_t host_port) + { + if (state_ > State::INITIALIZED) throw SocketError("Socket must be initialized before binding"); + + host_ = {host_ip, host_port}; + state_ = State::INITIALIZED; + return *this; + } + + /** + * @brief Set the socket to drop all packets not coming from `sender_ip` and `sender_port`. + * + * @param sender_ip The only allowed sender IP. Cannot be a multicast or broadcast address. + * @param sender_port The only allowed sender port. + */ + UdpSocket & limit_to_sender(const std::string & sender_ip, uint16_t sender_port) + { + if (state_ > State::INITIALIZED) throw SocketError("Buffer size has to be set before binding"); + + sender_.emplace(Endpoint{sender_ip, sender_port}); + return *this; + } + + /** + * @brief Set the MTU this socket supports. While this can be set arbitrarily, it is best set to + * the MTU of the network interface, or to the maximum expected packet length. + * + * @param bytes The MTU size. The default value is 1500. + */ + UdpSocket & set_mtu(size_t bytes) + { + if (state_ > State::INITIALIZED) throw SocketError("Buffer size has to be set before binding"); + + buffer_size_ = bytes; + return *this; + } + + /** + * @brief Join an IP multicast group. Only one group can be joined by the socket. + * + * @param group_ip The multicast IP. It has to be in the multicast range `224.0.0.0/4` (between + * `224.0.0.0` and `239.255.255.255`). + */ + UdpSocket & join_multicast_group(const std::string & group_ip) + { + if (state_ < State::INITIALIZED) throw SocketError("Socket has to be initialized first"); + + if (state_ >= State::BOUND) + throw SocketError("Multicast groups have to be joined before binding"); + + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(group_ip.c_str()); // Multicast group address + mreq.imr_interface.s_addr = inet_addr(host_.ip.c_str()); + + int result = setsockopt( + sock_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast(&mreq), sizeof(mreq)); + if (result < 0) throw SocketError((errno)); + + multicast_ip_.emplace(group_ip); + return *this; + } + + /** + * @brief Bind the socket to host IP and port given in `init()`. If `join_multicast_group()` was + * called before this function, the socket will be bound to `group_ip` instead. + */ + UdpSocket & bind() + { + if (state_ < State::INITIALIZED) throw SocketError("Socket has to be initialized first"); + + if (state_ >= State::BOUND) throw SocketError("Re-binding already bound socket"); + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(host_.port); + addr.sin_addr.s_addr = multicast_ip_ ? inet_addr(multicast_ip_->c_str()) : INADDR_ANY; + + int result = ::bind(sock_fd_, (struct sockaddr *)&addr, sizeof(addr)); + if (result == -1) throw SocketError((errno)); + return *this; + } + + /** + * @brief Register a callback for processing received packets and start the receiver thread. The + * callback will be called for each received packet, and will be executed in the receive thread. + * + * @param callback The function to be executed for each received packet. + */ + UdpSocket & subscribe(callback_t && callback) + { + callback_ = std::move(callback); + launch_receiver(); + return *this; + } + + ~UdpSocket() + { + if (state_ == State::ACTIVE) state_ = State::BOUND; + if (receive_thread_.joinable()) receive_thread_.join(); + close(sock_fd_); + } + +private: + void launch_receiver() + { + assert(state_ == State::BOUND); + assert(callback_); + + state_ = State::ACTIVE; + receive_thread_ = std::thread([this]() { + std::vector buffer; + while (state_ == State::ACTIVE) { + buffer.resize(buffer_size_); + auto msg_header = make_msg_header(buffer); + + ssize_t received = recvmsg(sock_fd_, &msg_header.msg, 0); + if (received < 0) throw SocketError((errno)); + if (!is_accepted_sender(msg_header.sender_addr)) continue; + + auto timestamp_ns_opt = get_receive_timestamp(msg_header.msg); + if (!timestamp_ns_opt) continue; + + uint64_t timestamp_ns = timestamp_ns_opt.value(); + + buffer.resize(received); + callback_(buffer, timestamp_ns); + } + }); + } + + std::optional get_receive_timestamp(msghdr & msg) + { + timeval const * tv = nullptr; + for (cmsghdr * cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP) { + tv = (struct timeval *)CMSG_DATA(cmsg); + break; + } + } + + if (!tv) { + return {}; + } + + uint64_t timestamp_ns = tv->tv_sec * 1'000'000'000 + tv->tv_usec * 1000; + return timestamp_ns; + } + + bool is_accepted_sender(const sockaddr_in & sender_addr) + { + if (!sender_) return true; + + std::array sender_name; + inet_ntop(AF_INET, &sender_addr.sin_addr, sender_name.data(), INET_ADDRSTRLEN); + return std::strncmp(sender_->ip.c_str(), sender_name.data(), INET_ADDRSTRLEN) == 0; + } + + MsgBuffers make_msg_header(std::vector & receive_buffer) const + { + msghdr msg{}; + iovec iov{}; + std::array control; + + sockaddr_in sender_addr; + socklen_t sender_addr_len = sizeof(sender_addr); + + iov.iov_base = receive_buffer.data(); + iov.iov_len = receive_buffer.size(); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.data(); + msg.msg_controllen = control.size(); + msg.msg_name = &sender_addr; + msg.msg_namelen = sender_addr_len; + + return MsgBuffers{msg, iov, control, sender_addr}; + } + + std::atomic state_{State::UNINITIALIZED}; + int sock_fd_; + size_t buffer_size_ = 1500; + Endpoint host_; + std::optional multicast_ip_; + std::optional sender_; + std::thread receive_thread_; + callback_t callback_; +}; + +} // namespace nebula::drivers::connections From 31cf927ede5d4c435b73d169d7278887c0ddf59c Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 11:50:51 +0900 Subject: [PATCH 06/36] chore(udp): more error handling and doc comments Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index c05b13f5..9335e1e8 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -158,7 +158,8 @@ class UdpSocket /** * @brief Bind the socket to host IP and port given in `init()`. If `join_multicast_group()` was - * called before this function, the socket will be bound to `group_ip` instead. + * called before this function, the socket will be bound to `group_ip` instead. At least `init()` + * has to have been called before. */ UdpSocket & bind() { @@ -179,11 +180,16 @@ class UdpSocket /** * @brief Register a callback for processing received packets and start the receiver thread. The * callback will be called for each received packet, and will be executed in the receive thread. + * Has to be called on a bound socket (`bind()` has to have been called before). * * @param callback The function to be executed for each received packet. */ UdpSocket & subscribe(callback_t && callback) { + if (state_ < State::BOUND) throw SocketError("Socket has to be bound first"); + + if (state_ > State::BOUND) throw SocketError("Cannot re-subscribe to socket"); + callback_ = std::move(callback); launch_receiver(); return *this; From 7b01925b5659674d6331be387411bf683656ebea Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 11:53:52 +0900 Subject: [PATCH 07/36] chore(udp): always enable socket reuse Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 9335e1e8..1cf6e82d 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -84,6 +84,10 @@ class UdpSocket int result = setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0; if (result < 0) throw SocketError((errno)); + int reuse = 1; + result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + if (result < 0) throw SocketError((errno)); + sock_fd_ = sock_fd; } From 2b10e1693d0e7b8a087d246cf717c1f0c34eeff1 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 11:54:16 +0900 Subject: [PATCH 08/36] chore(udp): clean up C-style code Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 1cf6e82d..aca6131a 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -148,12 +148,11 @@ class UdpSocket if (state_ >= State::BOUND) throw SocketError("Multicast groups have to be joined before binding"); - struct ip_mreq mreq; + ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(group_ip.c_str()); // Multicast group address mreq.imr_interface.s_addr = inet_addr(host_.ip.c_str()); - int result = setsockopt( - sock_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast(&mreq), sizeof(mreq)); + int result = setsockopt(sock_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (result < 0) throw SocketError((errno)); multicast_ip_.emplace(group_ip); From 8e61872ed4b2c7e7f5595af3d3c8797c3cb7e9c8 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 14:13:39 +0900 Subject: [PATCH 09/36] chore(udp): remove unnecessary double parens Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index aca6131a..04d7f737 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -82,11 +82,11 @@ class UdpSocket int enable = 1; int result = setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0; - if (result < 0) throw SocketError((errno)); + if (result < 0) throw SocketError(errno); int reuse = 1; result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - if (result < 0) throw SocketError((errno)); + if (result < 0) throw SocketError(errno); sock_fd_ = sock_fd; } @@ -153,7 +153,7 @@ class UdpSocket mreq.imr_interface.s_addr = inet_addr(host_.ip.c_str()); int result = setsockopt(sock_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); - if (result < 0) throw SocketError((errno)); + if (result < 0) throw SocketError(errno); multicast_ip_.emplace(group_ip); return *this; @@ -176,7 +176,7 @@ class UdpSocket addr.sin_addr.s_addr = multicast_ip_ ? inet_addr(multicast_ip_->c_str()) : INADDR_ANY; int result = ::bind(sock_fd_, (struct sockaddr *)&addr, sizeof(addr)); - if (result == -1) throw SocketError((errno)); + if (result == -1) throw SocketError(errno); return *this; } @@ -219,7 +219,7 @@ class UdpSocket auto msg_header = make_msg_header(buffer); ssize_t received = recvmsg(sock_fd_, &msg_header.msg, 0); - if (received < 0) throw SocketError((errno)); + if (received < 0) throw SocketError(errno); if (!is_accepted_sender(msg_header.sender_addr)) continue; auto timestamp_ns_opt = get_receive_timestamp(msg_header.msg); From 5e01ad50f08e2fbb426d66bebf02bae9fa8409bc Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 14:14:32 +0900 Subject: [PATCH 10/36] feat(udp): use poll to prevent blocking when there is no received data Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 04d7f737..121d71f3 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -14,8 +14,11 @@ #pragma once +#include + #include #include +#include #include #include @@ -68,6 +71,8 @@ class UdpSocket enum class State { UNINITIALIZED, INITIALIZED, BOUND, ACTIVE }; + static const int g_poll_timeout_ms = 10; + public: using callback_t = std::function &, uint64_t)>; @@ -88,6 +93,7 @@ class UdpSocket result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (result < 0) throw SocketError(errno); + poll_fd_ = {sock_fd, POLLIN, 0}; sock_fd_ = sock_fd; } @@ -215,6 +221,10 @@ class UdpSocket receive_thread_ = std::thread([this]() { std::vector buffer; while (state_ == State::ACTIVE) { + auto data_available = is_data_available(); + if (!data_available.has_value()) throw SocketError(data_available.error()); + if (!data_available.value()) continue; + buffer.resize(buffer_size_); auto msg_header = make_msg_header(buffer); @@ -251,6 +261,13 @@ class UdpSocket return timestamp_ns; } + util::expected is_data_available() + { + int status = poll(&poll_fd_, 1, g_poll_timeout_ms); + if (status < 0) return errno; + return (poll_fd_.revents & POLLIN) && (status > 0); + } + bool is_accepted_sender(const sockaddr_in & sender_addr) { if (!sender_) return true; @@ -283,7 +300,10 @@ class UdpSocket } std::atomic state_{State::UNINITIALIZED}; + int sock_fd_; + pollfd poll_fd_; + size_t buffer_size_ = 1500; Endpoint host_; std::optional multicast_ip_; From ad11905216733c90c16a189b4943690f088d27c3 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 14:33:59 +0900 Subject: [PATCH 11/36] chore(udp): differentiate between socket and usage-related errors Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 121d71f3..e842d77d 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,12 @@ class SocketError : public std::exception std::string what_; }; +class UsageError : public std::runtime_error +{ +public: + explicit UsageError(const std::string & msg) : std::runtime_error(msg) {} +}; + class UdpSocket { private: @@ -106,7 +113,7 @@ class UdpSocket */ UdpSocket & init(const std::string & host_ip, uint16_t host_port) { - if (state_ > State::INITIALIZED) throw SocketError("Socket must be initialized before binding"); + if (state_ > State::INITIALIZED) throw UsageError("Socket must be initialized before binding"); host_ = {host_ip, host_port}; state_ = State::INITIALIZED; @@ -121,7 +128,7 @@ class UdpSocket */ UdpSocket & limit_to_sender(const std::string & sender_ip, uint16_t sender_port) { - if (state_ > State::INITIALIZED) throw SocketError("Buffer size has to be set before binding"); + if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); sender_.emplace(Endpoint{sender_ip, sender_port}); return *this; @@ -135,7 +142,7 @@ class UdpSocket */ UdpSocket & set_mtu(size_t bytes) { - if (state_ > State::INITIALIZED) throw SocketError("Buffer size has to be set before binding"); + if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); buffer_size_ = bytes; return *this; @@ -149,10 +156,10 @@ class UdpSocket */ UdpSocket & join_multicast_group(const std::string & group_ip) { - if (state_ < State::INITIALIZED) throw SocketError("Socket has to be initialized first"); + if (state_ < State::INITIALIZED) throw UsageError("Socket has to be initialized first"); if (state_ >= State::BOUND) - throw SocketError("Multicast groups have to be joined before binding"); + throw UsageError("Multicast groups have to be joined before binding"); ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(group_ip.c_str()); // Multicast group address @@ -172,9 +179,9 @@ class UdpSocket */ UdpSocket & bind() { - if (state_ < State::INITIALIZED) throw SocketError("Socket has to be initialized first"); + if (state_ < State::INITIALIZED) throw UsageError("Socket has to be initialized first"); - if (state_ >= State::BOUND) throw SocketError("Re-binding already bound socket"); + if (state_ >= State::BOUND) throw UsageError("Re-binding already bound socket"); sockaddr_in addr{}; addr.sin_family = AF_INET; @@ -195,9 +202,9 @@ class UdpSocket */ UdpSocket & subscribe(callback_t && callback) { - if (state_ < State::BOUND) throw SocketError("Socket has to be bound first"); + if (state_ < State::BOUND) throw UsageError("Socket has to be bound first"); - if (state_ > State::BOUND) throw SocketError("Cannot re-subscribe to socket"); + if (state_ > State::BOUND) throw UsageError("Cannot re-subscribe to socket"); callback_ = std::move(callback); launch_receiver(); From a8d9f2286f1b00596d4c23c3bcfd9fb3397024ba Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 14:34:32 +0900 Subject: [PATCH 12/36] feat(udp): allow setting receive buffer size Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index e842d77d..f8ff68df 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -148,6 +148,16 @@ class UdpSocket return *this; } + UdpSocket & set_socket_buffer_size(size_t bytes) + { + if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); + + auto buf_size = static_cast(bytes); + int result = setsockopt(sock_fd_, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)); + if (result < 0) throw SocketError(errno); + return *this; + } + /** * @brief Join an IP multicast group. Only one group can be joined by the socket. * From 72033a71b20fcb3fd7c228774266304d20da9a4b Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 15:13:52 +0900 Subject: [PATCH 13/36] chore(udp): use uint8_t because std::byte is annoying to refactor into everywhere Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index f8ff68df..7943aded 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -81,7 +81,7 @@ class UdpSocket static const int g_poll_timeout_ms = 10; public: - using callback_t = std::function &, uint64_t)>; + using callback_t = std::function &, uint64_t)>; /** * @brief Construct a UDP socket with timestamp measuring enabled. The minimal way to start @@ -167,7 +167,6 @@ class UdpSocket UdpSocket & join_multicast_group(const std::string & group_ip) { if (state_ < State::INITIALIZED) throw UsageError("Socket has to be initialized first"); - if (state_ >= State::BOUND) throw UsageError("Multicast groups have to be joined before binding"); @@ -190,7 +189,6 @@ class UdpSocket UdpSocket & bind() { if (state_ < State::INITIALIZED) throw UsageError("Socket has to be initialized first"); - if (state_ >= State::BOUND) throw UsageError("Re-binding already bound socket"); sockaddr_in addr{}; @@ -213,7 +211,6 @@ class UdpSocket UdpSocket & subscribe(callback_t && callback) { if (state_ < State::BOUND) throw UsageError("Socket has to be bound first"); - if (state_ > State::BOUND) throw UsageError("Cannot re-subscribe to socket"); callback_ = std::move(callback); @@ -236,7 +233,7 @@ class UdpSocket state_ = State::ACTIVE; receive_thread_ = std::thread([this]() { - std::vector buffer; + std::vector buffer; while (state_ == State::ACTIVE) { auto data_available = is_data_available(); if (!data_available.has_value()) throw SocketError(data_available.error()); @@ -294,7 +291,7 @@ class UdpSocket return std::strncmp(sender_->ip.c_str(), sender_name.data(), INET_ADDRSTRLEN) == 0; } - MsgBuffers make_msg_header(std::vector & receive_buffer) const + MsgBuffers make_msg_header(std::vector & receive_buffer) const { msghdr msg{}; iovec iov{}; From 05bbcbbf73332b03ee9943b29ab8fc5b9927dc96 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 15:28:25 +0900 Subject: [PATCH 14/36] fix(udp): update state correctly when `bind()` is called Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 7943aded..305e94d3 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -198,6 +198,8 @@ class UdpSocket int result = ::bind(sock_fd_, (struct sockaddr *)&addr, sizeof(addr)); if (result == -1) throw SocketError(errno); + + state_ = State::BOUND; return *this; } From cd79905f3915ca2a45f6132d05c8189ced203d52 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 17:09:43 +0900 Subject: [PATCH 15/36] feat(udp): monitor socket packet drops Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 305e94d3..6015f332 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -76,12 +76,37 @@ class UdpSocket sockaddr_in sender_addr; }; + class DropMonitor + { + uint32_t last_drop_counter_{0}; + + public: + uint32_t get_drops_since_last_receive(uint32_t current_drop_counter) + { + uint32_t last = last_drop_counter_; + last_drop_counter_ = current_drop_counter; + + bool counter_did_wrap = current_drop_counter < last; + if (counter_did_wrap) { + return (UINT32_MAX - last) + current_drop_counter; + } + + return current_drop_counter - last; + } + }; + enum class State { UNINITIALIZED, INITIALIZED, BOUND, ACTIVE }; static const int g_poll_timeout_ms = 10; public: - using callback_t = std::function &, uint64_t)>; + struct ReceiveMetadata + { + std::optional timestamp_ns; + uint64_t drops_since_last_receive{0}; + }; + + using callback_t = std::function &, const ReceiveMetadata &)>; /** * @brief Construct a UDP socket with timestamp measuring enabled. The minimal way to start @@ -93,11 +118,15 @@ class UdpSocket if (sock_fd == -1) throw SocketError(errno); int enable = 1; - int result = setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0; + int result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + if (result < 0) throw SocketError(errno); + + // Enable kernel-space receive time measurement + result = setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)); if (result < 0) throw SocketError(errno); - int reuse = 1; - result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + // Enable reporting on packets dropped due to full UDP receive buffer + result = setsockopt(sock_fd, SOL_SOCKET, SO_RXQ_OVFL, &enable, sizeof(enable)); if (result < 0) throw SocketError(errno); poll_fd_ = {sock_fd, POLLIN, 0}; @@ -248,33 +277,38 @@ class UdpSocket if (received < 0) throw SocketError(errno); if (!is_accepted_sender(msg_header.sender_addr)) continue; - auto timestamp_ns_opt = get_receive_timestamp(msg_header.msg); - if (!timestamp_ns_opt) continue; - - uint64_t timestamp_ns = timestamp_ns_opt.value(); + auto metadata = get_receive_metadata(msg_header.msg); buffer.resize(received); - callback_(buffer, timestamp_ns); + callback_(buffer, metadata); } }); } - std::optional get_receive_timestamp(msghdr & msg) + ReceiveMetadata get_receive_metadata(msghdr & msg) { - timeval const * tv = nullptr; + ReceiveMetadata result; for (cmsghdr * cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP) { - tv = (struct timeval *)CMSG_DATA(cmsg); - break; + if (cmsg->cmsg_level != SOL_SOCKET) continue; + + switch (cmsg->cmsg_type) { + case SO_TIMESTAMP: { + timeval const * tv = (timeval const *)CMSG_DATA(cmsg); + uint64_t timestamp_ns = tv->tv_sec * 1'000'000'000 + tv->tv_usec * 1000; + result.timestamp_ns.emplace(timestamp_ns); + break; + } + case SO_RXQ_OVFL: { + uint32_t const * drops = (uint32_t const *)CMSG_DATA(cmsg); + result.drops_since_last_receive = drop_monitor_.get_drops_since_last_receive(*drops); + break; + } + default: + continue; } } - if (!tv) { - return {}; - } - - uint64_t timestamp_ns = tv->tv_sec * 1'000'000'000 + tv->tv_usec * 1000; - return timestamp_ns; + return result; } util::expected is_data_available() @@ -326,6 +360,8 @@ class UdpSocket std::optional sender_; std::thread receive_thread_; callback_t callback_; + + DropMonitor drop_monitor_; }; } // namespace nebula::drivers::connections From 7431a018116c05bf01609a8aa7f213cf5433064e Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Tue, 19 Nov 2024 17:42:06 +0900 Subject: [PATCH 16/36] feat(udp): add explicit unsubscribe function to facilitate clean shutdown Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 6015f332..adeed685 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -249,10 +249,20 @@ class UdpSocket return *this; } - ~UdpSocket() + /** + * @brief Gracefully stops the active receiver thread (if any) but keeps the socket alive. The + * same socket can later be subscribed again. + */ + UdpSocket & unsubscribe() { if (state_ == State::ACTIVE) state_ = State::BOUND; if (receive_thread_.joinable()) receive_thread_.join(); + return *this; + } + + ~UdpSocket() + { + unsubscribe(); close(sock_fd_); } From 115e31a1fa3eb3096337959ac46d99a7ff1c099e Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 11:01:29 +0900 Subject: [PATCH 17/36] chore(expected): add stdexcept include Signed-off-by: Max SCHMELLER --- nebula_common/include/nebula_common/util/expected.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/nebula_common/include/nebula_common/util/expected.hpp b/nebula_common/include/nebula_common/util/expected.hpp index 1d433344..45ed0b59 100644 --- a/nebula_common/include/nebula_common/util/expected.hpp +++ b/nebula_common/include/nebula_common/util/expected.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include From a5063665aa043adfe02bf3bc2dffe6611e565ab7 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 11:05:46 +0900 Subject: [PATCH 18/36] feat(udp): report when messages have been truncated Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index adeed685..aef16a64 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -104,6 +104,7 @@ class UdpSocket { std::optional timestamp_ns; uint64_t drops_since_last_receive{0}; + bool truncated; }; using callback_t = std::function &, const ReceiveMetadata &)>; @@ -283,11 +284,13 @@ class UdpSocket buffer.resize(buffer_size_); auto msg_header = make_msg_header(buffer); - ssize_t received = recvmsg(sock_fd_, &msg_header.msg, 0); + ssize_t received = recvmsg(sock_fd_, &msg_header.msg, MSG_TRUNC); if (received < 0) throw SocketError(errno); if (!is_accepted_sender(msg_header.sender_addr)) continue; - auto metadata = get_receive_metadata(msg_header.msg); + ReceiveMetadata metadata; + get_receive_metadata(msg_header.msg, metadata); + metadata.truncated = static_cast(received) > buffer_size_; buffer.resize(received); callback_(buffer, metadata); @@ -295,30 +298,28 @@ class UdpSocket }); } - ReceiveMetadata get_receive_metadata(msghdr & msg) + void get_receive_metadata(msghdr & msg, ReceiveMetadata & inout_metadata) { - ReceiveMetadata result; for (cmsghdr * cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) continue; switch (cmsg->cmsg_type) { case SO_TIMESTAMP: { - timeval const * tv = (timeval const *)CMSG_DATA(cmsg); + auto tv = (timeval const *)CMSG_DATA(cmsg); uint64_t timestamp_ns = tv->tv_sec * 1'000'000'000 + tv->tv_usec * 1000; - result.timestamp_ns.emplace(timestamp_ns); + inout_metadata.timestamp_ns.emplace(timestamp_ns); break; } case SO_RXQ_OVFL: { - uint32_t const * drops = (uint32_t const *)CMSG_DATA(cmsg); - result.drops_since_last_receive = drop_monitor_.get_drops_since_last_receive(*drops); + auto drops = (uint32_t const *)CMSG_DATA(cmsg); + inout_metadata.drops_since_last_receive = + drop_monitor_.get_drops_since_last_receive(*drops); break; } default: continue; } } - - return result; } util::expected is_data_available() From 7d2ddca47f5749208549f5dc0a554595fdc046b1 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 11:06:06 +0900 Subject: [PATCH 19/36] chore(udp): relax some usage requirements Signed-off-by: Max SCHMELLER --- .../nebula_hw_interfaces_common/connections/udp.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index aef16a64..0261e069 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -158,7 +158,7 @@ class UdpSocket */ UdpSocket & limit_to_sender(const std::string & sender_ip, uint16_t sender_port) { - if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); + if (state_ >= State::ACTIVE) throw UsageError("Sender has to be set before subscribing"); sender_.emplace(Endpoint{sender_ip, sender_port}); return *this; @@ -172,7 +172,7 @@ class UdpSocket */ UdpSocket & set_mtu(size_t bytes) { - if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); + if (state_ >= State::ACTIVE) throw UsageError("MTU size has to be set before subscribing"); buffer_size_ = bytes; return *this; @@ -181,6 +181,8 @@ class UdpSocket UdpSocket & set_socket_buffer_size(size_t bytes) { if (state_ > State::INITIALIZED) throw UsageError("Buffer size has to be set before binding"); + if (bytes > static_cast(INT32_MAX)) + throw UsageError("The maximum value supported (0x7FFFFFF) has been exceeded"); auto buf_size = static_cast(bytes); int result = setsockopt(sock_fd_, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)); From 390930dbde1f7b787cae8a253d03105093f16b20 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 11:07:09 +0900 Subject: [PATCH 20/36] test(udp): add most of the unit tests for udp socket Signed-off-by: Max SCHMELLER --- nebula_hw_interfaces/CMakeLists.txt | 21 +- nebula_hw_interfaces/test/common/test_udp.cpp | 184 ++++++++++++++++++ .../test/common/test_udp/utils.hpp | 78 ++++++++ 3 files changed, 277 insertions(+), 6 deletions(-) create mode 100644 nebula_hw_interfaces/test/common/test_udp.cpp create mode 100644 nebula_hw_interfaces/test/common/test_udp/utils.hpp diff --git a/nebula_hw_interfaces/CMakeLists.txt b/nebula_hw_interfaces/CMakeLists.txt index 2577ea9a..f5dc6569 100644 --- a/nebula_hw_interfaces/CMakeLists.txt +++ b/nebula_hw_interfaces/CMakeLists.txt @@ -2,13 +2,13 @@ cmake_minimum_required(VERSION 3.14) project(nebula_hw_interfaces) # Default to C++17 -if (NOT CMAKE_CXX_STANDARD) +if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 17) -endif () +endif() -if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -Wunused-function) -endif () +endif() find_package(ament_cmake_auto REQUIRED) find_package(boost_tcp_driver) @@ -53,7 +53,6 @@ target_link_libraries(nebula_hw_interfaces_velodyne PUBLIC ${boost_tcp_driver_LIBRARIES} ${boost_udp_driver_LIBRARIES} ${velodyne_msgs_TARGETS} - ) target_include_directories(nebula_hw_interfaces_velodyne PUBLIC ${boost_udp_driver_INCLUDE_DIRS} @@ -68,7 +67,6 @@ target_link_libraries(nebula_hw_interfaces_robosense PUBLIC ${boost_tcp_driver_LIBRARIES} ${boost_udp_driver_LIBRARIES} ${robosense_msgs_TARGETS} - ) target_include_directories(nebula_hw_interfaces_robosense PUBLIC ${boost_udp_driver_INCLUDE_DIRS} @@ -100,6 +98,17 @@ install(DIRECTORY include/ DESTINATION include/${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() + + find_package(ament_cmake_gtest REQUIRED) + + ament_add_gtest(test_udp + test/common/test_udp.cpp + ) + + target_include_directories(test_udp PUBLIC + ${nebula_common_INCLUDE_DIRS} + include + test) endif() ament_export_include_directories("include/${PROJECT_NAME}") diff --git a/nebula_hw_interfaces/test/common/test_udp.cpp b/nebula_hw_interfaces/test/common/test_udp.cpp new file mode 100644 index 00000000..d2fd61aa --- /dev/null +++ b/nebula_hw_interfaces/test/common/test_udp.cpp @@ -0,0 +1,184 @@ +// Copyright 2024 TIER IV, Inc. + +#include "common/test_udp/utils.hpp" +#include "nebula_common/util/expected.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nebula::drivers::connections +{ + +using std::chrono_literals::operator""ms; + +static const char localhost_ip[] = "127.0.0.1"; +static const char broadcast_ip[] = "255.255.255.255"; +static const char any_ip[] = "0.0.0.0"; +static const char multicast_group[] = "230.1.2.3"; + +static const char sender_ip[] = "192.168.201"; +static const uint16_t sender_port = 7373; +static const uint16_t host_port = 6262; + +static const std::chrono::duration send_receive_timeout = 100ms; + +UdpSocket::callback_t empty_cb() +{ + return [](const auto &, const auto &) {}; +} + +util::expected read_sys_param(const std::string & param_fqn) +{ + std::string path = "/proc/sys/" + param_fqn; + std::replace(path.begin(), path.end(), '.', '/'); + std::ifstream ifs{path}; + if (!ifs) return "could not read " + param_fqn; + + size_t param{}; + if (!(ifs >> param)) return param_fqn + " has unrecognized format"; + return param; +} + +TEST(test_udp, test_basic_lifecycle) +{ + ASSERT_NO_THROW( + UdpSocket().init(localhost_ip, host_port).bind().subscribe(empty_cb()).unsubscribe()); +} + +TEST(test_udp, test_special_addresses_bind) +{ + ASSERT_NO_THROW(UdpSocket().init(broadcast_ip, host_port).bind()); + ASSERT_NO_THROW(UdpSocket().init(any_ip, host_port).bind()); +} + +TEST(test_udp, test_wildcard_multicast_join_error) +{ + ASSERT_THROW( + UdpSocket().init(broadcast_ip, host_port).join_multicast_group(multicast_group).bind(), + SocketError); +} + +TEST(test_udp, test_buffer_resize) +{ + auto rmem_max_maybe = read_sys_param("net.core.rmem_max"); + if (!rmem_max_maybe.has_value()) GTEST_SKIP() << rmem_max_maybe.error(); + size_t rmem_max = rmem_max_maybe.value(); + + // Setting buffer sizes up to and including rmem_max shall succeed + ASSERT_NO_THROW( + UdpSocket().init(localhost_ip, host_port).set_socket_buffer_size(rmem_max).bind()); + + // Linux only supports sizes up to INT32_MAX + ASSERT_THROW( + UdpSocket() + .init(localhost_ip, host_port) + .set_socket_buffer_size(static_cast(INT32_MAX) + 1) + .bind(), + UsageError); +} + +TEST(test_udp, test_correct_usage_is_enforced) +{ + // These functions require other functions (e.g. `init()`) to be called beforehand + ASSERT_THROW(UdpSocket().bind(), UsageError); + ASSERT_THROW(UdpSocket().join_multicast_group(multicast_group), UsageError); + ASSERT_THROW(UdpSocket().subscribe(empty_cb()), UsageError); + + // The following functions can be called in any order, any number of times + ASSERT_NO_THROW(UdpSocket().limit_to_sender(sender_ip, sender_port)); + ASSERT_NO_THROW(UdpSocket().init(localhost_ip, host_port)); + ASSERT_NO_THROW(UdpSocket() + .limit_to_sender(sender_ip, sender_port) + .init(localhost_ip, host_port) + .limit_to_sender(sender_ip, sender_port) + .init(localhost_ip, host_port) + .bind()); + + // Sockets cannot be re-bound + ASSERT_THROW(UdpSocket().init(localhost_ip, host_port).bind().bind(), UsageError); + + ASSERT_THROW( + UdpSocket().init(localhost_ip, host_port).bind().init(localhost_ip, host_port), UsageError); + ASSERT_NO_THROW( + UdpSocket().init(localhost_ip, host_port).bind().limit_to_sender(sender_ip, sender_port)); + + // Only bound sockets can be subscribed + ASSERT_THROW(UdpSocket().init(localhost_ip, host_port).subscribe(empty_cb()), UsageError); + ASSERT_NO_THROW(UdpSocket().init(localhost_ip, host_port).bind().subscribe(empty_cb())); + + // Only one callback can exist + ASSERT_THROW( + UdpSocket().init(localhost_ip, host_port).bind().subscribe(empty_cb()).subscribe(empty_cb()), + UsageError); + + // But un- and re-subscribing shall be supported + ASSERT_NO_THROW(UdpSocket() + .init(localhost_ip, host_port) + .bind() + .subscribe(empty_cb()) + .unsubscribe() + .subscribe(empty_cb())); + + // Unsubscribing on a non-subscribed socket shall also be supported + ASSERT_NO_THROW(UdpSocket().unsubscribe()); +} + +TEST(test_udp, test_receiving) +{ + const std::vector payload{1, 2, 3}; + UdpSocket sock{}; + sock.init(localhost_ip, host_port).bind(); + + auto err_no_opt = udp_send(localhost_ip, host_port, payload); + if (err_no_opt.has_value()) GTEST_SKIP() << strerror(err_no_opt.value()); + + auto result_opt = receive_once(sock, send_receive_timeout); + + ASSERT_TRUE(result_opt.has_value()); + auto const & [recv_payload, metadata] = result_opt.value(); + ASSERT_EQ(recv_payload, payload); + ASSERT_FALSE(metadata.truncated); + ASSERT_EQ(metadata.drops_since_last_receive, 0); + + // TODO(mojomex): currently cannot test timestamping on loopback interface (no timestamp produced) +} + +TEST(test_udp, test_receiving_oversized) +{ + const size_t mtu = 1500; + std::vector payload; + payload.resize(mtu + 1); + UdpSocket sock{}; + sock.init(localhost_ip, host_port).set_mtu(mtu).bind(); + + auto err_no_opt = udp_send(localhost_ip, host_port, payload); + if (err_no_opt.has_value()) GTEST_SKIP() << strerror(err_no_opt.value()); + + auto result_opt = receive_once(sock, send_receive_timeout); + + ASSERT_TRUE(result_opt.has_value()); + auto const & [recv_payload, metadata] = result_opt.value(); + ASSERT_TRUE(std::equal(recv_payload.begin(), recv_payload.end(), payload.begin())); + ASSERT_TRUE(metadata.truncated); + ASSERT_EQ(metadata.drops_since_last_receive, 0); +} + +} // namespace nebula::drivers::connections + +int main(int argc, char * argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}; \ No newline at end of file diff --git a/nebula_hw_interfaces/test/common/test_udp/utils.hpp b/nebula_hw_interfaces/test/common/test_udp/utils.hpp new file mode 100644 index 00000000..dfb0fbb9 --- /dev/null +++ b/nebula_hw_interfaces/test/common/test_udp/utils.hpp @@ -0,0 +1,78 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +std::optional udp_send( + const char * to_ip, uint16_t to_port, const std::vector & bytes) +{ + int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd < 0) return errno; + + int enable = 1; + int result = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + if (result < 0) return errno; + + sockaddr_in receiver_addr{}; + memset(&receiver_addr, 0, sizeof(receiver_addr)); + receiver_addr.sin_family = AF_INET; + receiver_addr.sin_port = htons(to_port); + receiver_addr.sin_addr.s_addr = inet_addr(to_ip); + + result = sendto( + sock_fd, bytes.data(), bytes.size(), 0, reinterpret_cast(&receiver_addr), + sizeof(receiver_addr)); + if (result < 0) return errno; + result = close(sock_fd); + if (result < 0) return errno; + return {}; +} + +template +std::optional< + std::pair, nebula::drivers::connections::UdpSocket::ReceiveMetadata>> +receive_once( + nebula::drivers::connections::UdpSocket & sock, std::chrono::duration<_T, _R> timeout) +{ + std::condition_variable cv_received_result; + std::mutex mtx_result; + std::optional< + std::pair, nebula::drivers::connections::UdpSocket::ReceiveMetadata>> + result; + + sock.subscribe([&](const auto & data, const auto & metadata) { + std::lock_guard lock(mtx_result); + result.emplace(data, metadata); + cv_received_result.notify_one(); + }); + + std::unique_lock lock(mtx_result); + cv_received_result.wait_for(lock, timeout, [&result]() { return result.has_value(); }); + sock.unsubscribe(); + return result; +} \ No newline at end of file From cda257e7f4d434168e4171ee368109721c38639d Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 11:09:08 +0900 Subject: [PATCH 21/36] chore(cspell): add OVFL to dictionary Signed-off-by: Max SCHMELLER --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index 4c14cb9f..f305225f 100644 --- a/.cspell.json +++ b/.cspell.json @@ -32,6 +32,7 @@ "nproc", "nsec", "ntoa", + "OVFL", "pandar", "PANDAR", "PANDARAT", From 95b8b772de25ababbdda9453f3434947f8d71d98 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 02:07:40 +0000 Subject: [PATCH 22/36] ci(pre-commit): autofix --- nebula_hw_interfaces/test/common/test_udp.cpp | 2 +- nebula_hw_interfaces/test/common/test_udp/utils.hpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nebula_hw_interfaces/test/common/test_udp.cpp b/nebula_hw_interfaces/test/common/test_udp.cpp index d2fd61aa..eb5f68cc 100644 --- a/nebula_hw_interfaces/test/common/test_udp.cpp +++ b/nebula_hw_interfaces/test/common/test_udp.cpp @@ -181,4 +181,4 @@ int main(int argc, char * argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -}; \ No newline at end of file +}; diff --git a/nebula_hw_interfaces/test/common/test_udp/utils.hpp b/nebula_hw_interfaces/test/common/test_udp/utils.hpp index dfb0fbb9..6b949219 100644 --- a/nebula_hw_interfaces/test/common/test_udp/utils.hpp +++ b/nebula_hw_interfaces/test/common/test_udp/utils.hpp @@ -53,11 +53,10 @@ std::optional udp_send( return {}; } -template +template std::optional< std::pair, nebula::drivers::connections::UdpSocket::ReceiveMetadata>> -receive_once( - nebula::drivers::connections::UdpSocket & sock, std::chrono::duration<_T, _R> timeout) +receive_once(nebula::drivers::connections::UdpSocket & sock, std::chrono::duration<_T, _R> timeout) { std::condition_variable cv_received_result; std::mutex mtx_result; @@ -75,4 +74,4 @@ receive_once( cv_received_result.wait_for(lock, timeout, [&result]() { return result.has_value(); }); sock.unsubscribe(); return result; -} \ No newline at end of file +} From e2b51eb32c9250352c5c13780a7b5e0ffaedff6a Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 14:06:30 +0900 Subject: [PATCH 23/36] fix(udp): return correctly truncated buffer when oversized packet is received Signed-off-by: Max SCHMELLER --- .../connections/udp.hpp | 11 +++++++---- nebula_hw_interfaces/test/common/test_udp.cpp | 16 +++++++++++++++- .../test/common/test_udp/utils.hpp | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index 0261e069..23cb4b92 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -286,15 +287,17 @@ class UdpSocket buffer.resize(buffer_size_); auto msg_header = make_msg_header(buffer); - ssize_t received = recvmsg(sock_fd_, &msg_header.msg, MSG_TRUNC); - if (received < 0) throw SocketError(errno); + ssize_t recv_result = recvmsg(sock_fd_, &msg_header.msg, MSG_TRUNC); + if (recv_result < 0) throw SocketError(errno); + size_t untruncated_packet_length = recv_result; + if (!is_accepted_sender(msg_header.sender_addr)) continue; ReceiveMetadata metadata; get_receive_metadata(msg_header.msg, metadata); - metadata.truncated = static_cast(received) > buffer_size_; + metadata.truncated = untruncated_packet_length > buffer_size_; - buffer.resize(received); + buffer.resize(std::min(buffer_size_, untruncated_packet_length)); callback_(buffer, metadata); } }); diff --git a/nebula_hw_interfaces/test/common/test_udp.cpp b/nebula_hw_interfaces/test/common/test_udp.cpp index eb5f68cc..67407724 100644 --- a/nebula_hw_interfaces/test/common/test_udp.cpp +++ b/nebula_hw_interfaces/test/common/test_udp.cpp @@ -159,7 +159,7 @@ TEST(test_udp, test_receiving_oversized) { const size_t mtu = 1500; std::vector payload; - payload.resize(mtu + 1); + payload.resize(mtu + 1, 0x42); UdpSocket sock{}; sock.init(localhost_ip, host_port).set_mtu(mtu).bind(); @@ -170,11 +170,25 @@ TEST(test_udp, test_receiving_oversized) ASSERT_TRUE(result_opt.has_value()); auto const & [recv_payload, metadata] = result_opt.value(); + ASSERT_EQ(recv_payload.size(), mtu); ASSERT_TRUE(std::equal(recv_payload.begin(), recv_payload.end(), payload.begin())); ASSERT_TRUE(metadata.truncated); ASSERT_EQ(metadata.drops_since_last_receive, 0); } +TEST(test_udp, test_filtering_sender) +{ + std::vector payload{1, 2, 3}; + UdpSocket sock{}; + sock.init(localhost_ip, host_port).bind().limit_to_sender(sender_ip, sender_port); + + auto err_no_opt = udp_send(localhost_ip, host_port, payload); + if (err_no_opt.has_value()) GTEST_SKIP() << strerror(err_no_opt.value()); + + auto result_opt = receive_once(sock, send_receive_timeout); + ASSERT_FALSE(result_opt.has_value()); +} + } // namespace nebula::drivers::connections int main(int argc, char * argv[]) diff --git a/nebula_hw_interfaces/test/common/test_udp/utils.hpp b/nebula_hw_interfaces/test/common/test_udp/utils.hpp index 6b949219..dc9a08c0 100644 --- a/nebula_hw_interfaces/test/common/test_udp/utils.hpp +++ b/nebula_hw_interfaces/test/common/test_udp/utils.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include std::optional udp_send( From 7b55ecd3803c1a52cabcdf031b3ad1181faca993 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 18:35:24 +0900 Subject: [PATCH 24/36] feat(hesai): add an easier-to-use TCP socket implementation (still uses transport_drivers internally) Signed-off-by: Max SCHMELLER --- .../connections/tcp.hpp | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp new file mode 100644 index 00000000..aa5f4598 --- /dev/null +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp @@ -0,0 +1,103 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +namespace nebula::drivers::connections +{ + +class TcpError : public std::runtime_error +{ +public: + explicit TcpError(const std::string & msg) : std::runtime_error(msg) {} +}; + +class AbstractTcpSocket +{ +public: + using header_callback_t = std::function &)>; + using payload_callback_t = std::function &)>; + using completion_callback_t = std::function; + + virtual ~AbstractTcpSocket() = default; + + virtual void init( + const std::string & host_ip, uint16_t host_port, const std::string & remote_ip, + uint16_t remote_port) = 0; + + virtual void bind() = 0; + + virtual void close() = 0; + + virtual void async_ptc_request( + std::vector & ptc_packet, header_callback_t cb_header, payload_callback_t cb_payload, + completion_callback_t cb_completion) = 0; +}; + +class TcpSocket : public AbstractTcpSocket +{ +public: + using callback_t = std::function &)>; + + void init( + const std::string & host_ip, uint16_t host_port, const std::string & remote_ip, + uint16_t remote_port) override + { + tcp_driver_.init_socket(remote_ip, remote_port, host_ip, host_port); + } + + void bind() override + { + if (!tcp_driver_.isOpen() && !tcp_driver_.open()) { + throw TcpError("could not open TCP socket for an unknown reason"); + } + } + + void close() override + { + if (tcp_driver_.isOpen()) { + tcp_driver_.close(); + } + } + + void async_ptc_request( + std::vector & ptc_packet, header_callback_t cb_header, payload_callback_t cb_payload, + completion_callback_t cb_completion) override + { + if (tcp_driver_.GetIOContext()->stopped()) { + tcp_driver_.GetIOContext()->restart(); + } + bool success = + tcp_driver_.asyncSendReceiveHeaderPayload(ptc_packet, cb_header, cb_payload, cb_completion); + if (!success) { + throw TcpError("sending the PTC command failed for an unknown reason"); + } + tcp_driver_.GetIOContext()->run(); + } + +private: + ::drivers::tcp_driver::TcpDriver tcp_driver_{std::make_shared(1)}; +}; + +} // namespace nebula::drivers::connections From 585f93b480ab36003778204ef23a365cfe2f3201 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 18:36:34 +0900 Subject: [PATCH 25/36] chore(hesai): use new TCP implementation and dependency-inject concrete TCP socket Signed-off-by: Max SCHMELLER --- .../hesai_hw_interface.hpp | 16 ++--- .../hesai_hw_interface.cpp | 59 ++++--------------- .../nebula_ros/hesai/hw_interface_wrapper.hpp | 1 + nebula_ros/src/hesai/hw_interface_wrapper.cpp | 3 +- 4 files changed, 19 insertions(+), 60 deletions(-) diff --git a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp index 8820a4f2..f1d7a75b 100644 --- a/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp +++ b/nebula_hw_interfaces/include/nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp @@ -17,6 +17,8 @@ // Have to define macros to silence warnings about deprecated headers being used by // boost/property_tree/ in some versions of boost. // See: https://github.com/boostorg/property_tree/issues/51 +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp" + #include #include @@ -31,7 +33,6 @@ #include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_cmd_response.hpp" #include -#include #include #include #include @@ -138,9 +139,8 @@ class HesaiHwInterface std::shared_ptr logger_; std::unique_ptr<::drivers::common::IoContext> cloud_io_context_; - std::shared_ptr m_owned_ctx; std::unique_ptr<::drivers::udp_driver::UdpDriver> cloud_udp_driver_; - std::shared_ptr<::drivers::tcp_driver::TcpDriver> tcp_driver_; + std::shared_ptr tcp_socket_; std::shared_ptr sensor_configuration_; std::function & buffer)> cloud_packet_callback_; /**This function pointer is called when the scan is complete*/ @@ -189,7 +189,9 @@ class HesaiHwInterface public: /// @brief Constructor - explicit HesaiHwInterface(std::shared_ptr logger); + HesaiHwInterface( + std::shared_ptr logger, + std::shared_ptr tcp_socket); /// @brief Destructor ~HesaiHwInterface(); /// @brief Initializing tcp_driver for TCP communication @@ -362,12 +364,6 @@ class HesaiHwInterface /// @return Resulting status HesaiLidarMonitor GetLidarMonitor(); - /// @brief Call run() of IO Context - void IOContextRun(); - /// @brief GetIO Context - /// @return IO Context - std::shared_ptr GetIOContext(); - /// @brief Setting spin_speed via HTTP API /// @param ctx IO Context used /// @param rpm spin_speed (300, 600, 1200) diff --git a/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp b/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp index b3ecc69e..19d3ff74 100644 --- a/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp +++ b/nebula_hw_interfaces/src/nebula_hesai_hw_interfaces/hesai_hw_interface.cpp @@ -36,12 +36,13 @@ namespace nebula::drivers using std::string_literals::operator""s; using nlohmann::json; -HesaiHwInterface::HesaiHwInterface(std::shared_ptr logger) +HesaiHwInterface::HesaiHwInterface( + std::shared_ptr logger, + std::shared_ptr tcp_socket) : logger_(std::move(logger)), cloud_io_context_{new ::drivers::common::IoContext(1)}, - m_owned_ctx{new boost::asio::io_context(1)}, cloud_udp_driver_{new ::drivers::udp_driver::UdpDriver(*cloud_io_context_)}, - tcp_driver_{new ::drivers::tcp_driver::TcpDriver(m_owned_ctx)}, + tcp_socket_{std::move(tcp_socket)}, target_model_no(NebulaModelToHesaiModelNo(SensorModel::UNKNOWN)) { } @@ -86,13 +87,8 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( std::timed_mutex tm; tm.lock(); - if (tcp_driver_->GetIOContext()->stopped()) { - logger_->debug(log_tag + "IOContext was stopped"); - tcp_driver_->GetIOContext()->restart(); - } - logger_->debug(log_tag + "Sending payload"); - tcp_driver_->asyncSendReceiveHeaderPayload( + tcp_socket_->async_ptc_request( send_buf, [this, log_tag, command_id, response_complete, error_code](const std::vector & header_bytes) { @@ -118,7 +114,7 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( // Header had payload length 0 (thus, header callback processed request successfully already), // but we still received a payload: invalid state - if (*response_complete == true) { + if (*response_complete) { error_code->error_flags |= TCP_ERROR_UNEXPECTED_PAYLOAD; return; } @@ -132,7 +128,6 @@ HesaiHwInterface::ptc_cmd_result_t HesaiHwInterface::SendReceive( tm.unlock(); logger_->debug(log_tag + "Unlocked mutex"); }); - this->IOContextRun(); if (!tm.try_lock_for(std::chrono::seconds(1))) { logger_->error(log_tag + "Request did not finish within 1s"); error_code->error_flags |= TCP_ERROR_TIMEOUT; @@ -244,40 +239,16 @@ Status HesaiHwInterface::GetCalibrationConfiguration( Status HesaiHwInterface::InitializeTcpDriver() { -#ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - std::cout << "HesaiHwInterface::InitializeTcpDriver" << std::endl; - std::cout << "st: tcp_driver_->init_socket" << std::endl; - std::cout << "sensor_configuration_->sensor_ip=" << sensor_configuration_->sensor_ip << std::endl; - std::cout << "sensor_configuration_->host_ip=" << sensor_configuration_->host_ip << std::endl; - std::cout << "PandarTcpCommandPort=" << PandarTcpCommandPort << std::endl; -#endif - tcp_driver_->init_socket( - sensor_configuration_->sensor_ip, PandarTcpCommandPort, sensor_configuration_->host_ip, + tcp_socket_->init( + sensor_configuration_->host_ip, PandarTcpCommandPort, sensor_configuration_->sensor_ip, PandarTcpCommandPort); -#ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - std::cout << "ed: tcp_driver_->init_socket" << std::endl; -#endif - if (!tcp_driver_->open()) { -#ifdef WITH_DEBUG_STDOUT_HESAI_HW_INTERFACE - std::cout << "!tcp_driver_->open()" << std::endl; -#endif - // tcp_driver_->close(); - tcp_driver_->closeSync(); - return Status::ERROR_1; - } + tcp_socket_->bind(); return Status::OK; } Status HesaiHwInterface::FinalizeTcpDriver() { - try { - if (tcp_driver_) { - tcp_driver_->close(); - } - } catch (std::exception & e) { - logger_->error("Error while finalizing the TcpDriver"); - return Status::UDP_CONNECTION_ERROR; - } + tcp_socket_->close(); return Status::OK; } @@ -717,16 +688,6 @@ HesaiLidarMonitor HesaiHwInterface::GetLidarMonitor() return CheckSizeAndParse(response); } -void HesaiHwInterface::IOContextRun() -{ - m_owned_ctx->run(); -} - -std::shared_ptr HesaiHwInterface::GetIOContext() -{ - return m_owned_ctx; -} - HesaiStatus HesaiHwInterface::GetHttpClientDriverOnce( std::shared_ptr ctx, std::unique_ptr<::drivers::tcp_driver::HttpClientDriver> & hcd) diff --git a/nebula_ros/include/nebula_ros/hesai/hw_interface_wrapper.hpp b/nebula_ros/include/nebula_ros/hesai/hw_interface_wrapper.hpp index 4b2572b1..399d92f4 100644 --- a/nebula_ros/include/nebula_ros/hesai/hw_interface_wrapper.hpp +++ b/nebula_ros/include/nebula_ros/hesai/hw_interface_wrapper.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include diff --git a/nebula_ros/src/hesai/hw_interface_wrapper.cpp b/nebula_ros/src/hesai/hw_interface_wrapper.cpp index a89a0a37..ef558f15 100644 --- a/nebula_ros/src/hesai/hw_interface_wrapper.cpp +++ b/nebula_ros/src/hesai/hw_interface_wrapper.cpp @@ -15,7 +15,8 @@ HesaiHwInterfaceWrapper::HesaiHwInterfaceWrapper( rclcpp::Node * const parent_node, std::shared_ptr & config, bool use_udp_only) : hw_interface_(std::make_shared( - drivers::loggers::RclcppLogger(parent_node->get_logger()).child("HwInterface"))), + drivers::loggers::RclcppLogger(parent_node->get_logger()).child("HwInterface"), + std::move(std::make_unique()))), logger_(parent_node->get_logger().get_child("HwInterfaceWrapper")), status_(Status::NOT_INITIALIZED), use_udp_only_(use_udp_only) From 81ad961d8e49ce625e73038ccfdc287738d68fcf Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Fri, 22 Nov 2024 18:38:09 +0900 Subject: [PATCH 26/36] test(hesai): add basic tests for Hesai's PTC protocol Signed-off-by: Max SCHMELLER --- nebula_hw_interfaces/CMakeLists.txt | 17 +++ nebula_hw_interfaces/package.xml | 1 + nebula_hw_interfaces/test/hesai/test_ptc.cpp | 115 ++++++++++++++++++ .../test/hesai/test_ptc/ptc_test.hpp | 50 ++++++++ .../test/hesai/test_ptc/tcp_mock.hpp | 47 +++++++ 5 files changed, 230 insertions(+) create mode 100644 nebula_hw_interfaces/test/hesai/test_ptc.cpp create mode 100644 nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp create mode 100644 nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp diff --git a/nebula_hw_interfaces/CMakeLists.txt b/nebula_hw_interfaces/CMakeLists.txt index f5dc6569..13efaaa1 100644 --- a/nebula_hw_interfaces/CMakeLists.txt +++ b/nebula_hw_interfaces/CMakeLists.txt @@ -100,6 +100,7 @@ if(BUILD_TESTING) ament_lint_auto_find_test_dependencies() find_package(ament_cmake_gtest REQUIRED) + find_package(ament_cmake_gmock REQUIRED) ament_add_gtest(test_udp test/common/test_udp.cpp @@ -109,6 +110,22 @@ if(BUILD_TESTING) ${nebula_common_INCLUDE_DIRS} include test) + + ament_add_gmock(hesai_test_ptc + test/hesai/test_ptc.cpp + ) + + target_include_directories(hesai_test_ptc PUBLIC + ${nebula_common_INCLUDE_DIRS} + ${nebula_hw_interfaces_hesai_INCLUDE_DIRS} + ${boost_tcp_driver_INCLUDE_DIRS} + ${boost_udp_driver_INCLUDE_DIRS} + include + test) + + target_link_libraries(hesai_test_ptc + nebula_hw_interfaces_hesai + ) endif() ament_export_include_directories("include/${PROJECT_NAME}") diff --git a/nebula_hw_interfaces/package.xml b/nebula_hw_interfaces/package.xml index f77e8876..049df32b 100644 --- a/nebula_hw_interfaces/package.xml +++ b/nebula_hw_interfaces/package.xml @@ -20,6 +20,7 @@ ros2_socketcan velodyne_msgs + ament_cmake_gmock ament_cmake_gtest ament_lint_auto diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp new file mode 100644 index 00000000..34e3544e --- /dev/null +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -0,0 +1,115 @@ +// Copyright 2024 TIER IV, Inc. + +#include "hesai/test_ptc/ptc_test.hpp" +#include "nebula_common/hesai/hesai_common.hpp" +#include "nebula_common/nebula_common.hpp" + +#include +#include +#include +#include + +#include + +namespace nebula::drivers +{ + +using testing::_; +using testing::AtLeast; +using testing::Exactly; +using testing::InSequence; + +const SensorModel g_models_under_test[] = { + SensorModel::HESAI_PANDAR64, SensorModel::HESAI_PANDAR40P, SensorModel::HESAI_PANDARQT64, + SensorModel::HESAI_PANDARQT128, SensorModel::HESAI_PANDARXT32, SensorModel::HESAI_PANDARAT128, + SensorModel::HESAI_PANDAR128_E4X, +}; + +const uint16_t g_u16_invalid = 0x4242; +const uint16_t g_ptc_port = 9347; +const char g_host_ip[] = "192.168.42.42"; +const char g_sensor_ip[] = "192.168.84.84"; + +auto make_sensor_config(SensorModel model) +{ + uint16_t rotation_speed = 600; + uint16_t sync_angle = 0; + double cut_angle = 0.0; + uint16_t cloud_min_angle = 0; + uint16_t cloud_max_angle = 360; + + if (model == SensorModel::HESAI_PANDARAT128) { + rotation_speed = 200; + sync_angle = 30; + cut_angle = 150.0; + cloud_min_angle = 30; + cloud_max_angle = 150; + } + + HesaiSensorConfiguration config{ + LidarConfigurationBase{ + EthernetSensorConfigurationBase{ + SensorConfigurationBase{model, "test"}, g_host_ip, g_sensor_ip, g_u16_invalid}, + ReturnMode::UNKNOWN, + g_u16_invalid, + g_u16_invalid, + CoordinateMode::UNKNOWN, + NAN, + NAN, + false, + {}, + false}, + "", + g_u16_invalid, + sync_angle, + cut_angle, + 0.1, + "", + rotation_speed, + cloud_min_angle, + cloud_max_angle, + PtpProfile::IEEE_802_1AS_AUTO, + 0, + PtpTransportType::L2, + PtpSwitchType::NON_TSN}; + + return std::make_shared(config); +} + +TEST_P(PtcTest, ConnectionLifecycle) +{ + /* Constructor does not immediately connect, destructor closes socket */ { + auto tcp_sock_ptr = make_mock_tcp_socket(); + auto & tcp_sock = *tcp_sock_ptr; + + EXPECT_CALL(tcp_sock, close()).Times(AtLeast(1)); + auto hw_interface = make_hw_interface(tcp_sock_ptr); + } + + /* Full lifecycle without sending/receiving */ { + auto tcp_sock_ptr = make_mock_tcp_socket(); + auto & tcp_sock = *tcp_sock_ptr; + + InSequence seq; + EXPECT_CALL(tcp_sock, init(g_host_ip, _, g_sensor_ip, g_ptc_port)).Times(Exactly(1)); + EXPECT_CALL(tcp_sock, bind()).Times(Exactly(1)); + EXPECT_CALL(tcp_sock, close()).Times(AtLeast(1)); + + auto cfg = make_sensor_config(GetParam()); + + auto hw_interface = make_hw_interface(tcp_sock_ptr); + hw_interface->SetSensorConfiguration(cfg); + hw_interface->InitializeTcpDriver(); + hw_interface->FinalizeTcpDriver(); + } +} + +INSTANTIATE_TEST_SUITE_P(TestMain, PtcTest, testing::ValuesIn(g_models_under_test)); + +} // namespace nebula::drivers + +int main(int argc, char * argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}; diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp new file mode 100644 index 00000000..0cbe4f96 --- /dev/null +++ b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp @@ -0,0 +1,50 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "hesai/test_ptc/tcp_mock.hpp" +#include "nebula_common/loggers/console_logger.hpp" +#include "nebula_common/nebula_common.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp" + +#include + +#include +#include + +namespace nebula::drivers +{ +class PtcTest : public ::testing::TestWithParam +{ +protected: + void SetUp() override {} + + void TearDown() override {} + + static auto make_mock_tcp_socket() { return std::make_shared(); } + + static auto make_hw_interface(std::shared_ptr tcp_socket) + { + auto model = GetParam(); + + auto logger = std::make_shared("HwInterface"); + + auto hw_interface = std::make_unique(logger, std::move(tcp_socket)); + hw_interface->SetTargetModel(hw_interface->NebulaModelToHesaiModelNo(model)); + return hw_interface; + } +}; + +} // namespace nebula::drivers diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp new file mode 100644 index 00000000..3916c13f --- /dev/null +++ b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp @@ -0,0 +1,47 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp" + +#include + +#include +#include + +namespace nebula::drivers::connections +{ + +class MockTcpSocket : public AbstractTcpSocket +{ +public: + MOCK_METHOD( + void, init, + (const std::string & host_ip, uint16_t host_port, const std::string & remote_ip, + uint16_t remote_port), + (override)); + + MOCK_METHOD(void, bind, (), (override)); + + MOCK_METHOD(void, close, (), (override)); + + MOCK_METHOD( + void, async_ptc_request, + (std::vector & ptc_packet, header_callback_t cb_header, payload_callback_t cb_payload, + completion_callback_t cb_completion), + (override)); +}; + +} // namespace nebula::drivers::connections From e35b856787fa00bf18e559bcff8adf8bed697a44 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Mon, 25 Nov 2024 20:04:51 +0900 Subject: [PATCH 27/36] WIP Signed-off-by: Max SCHMELLER --- nebula_hw_interfaces/CMakeLists.txt | 3 + nebula_hw_interfaces/package.xml | 1 + nebula_hw_interfaces/test/hesai/test_ptc.cpp | 34 +++- .../test/hesai/test_ptc/ptc_test.hpp | 4 +- .../{tcp_mock.hpp => tcp_socket_mock.hpp} | 0 .../test/hesai/test_ptc/tcp_socket_replay.hpp | 157 ++++++++++++++++++ .../test_resources/hesai/at128.json | 46 +++++ .../test_resources/hesai/ot128.json | 61 +++++++ .../test_resources/hesai/pandar40p.json | 63 +++++++ .../test_resources/hesai/pandar64.json | 56 +++++++ .../test_resources/hesai/qt128.json | 63 +++++++ .../test_resources/hesai/xt32.json | 70 ++++++++ 12 files changed, 553 insertions(+), 5 deletions(-) rename nebula_hw_interfaces/test/hesai/test_ptc/{tcp_mock.hpp => tcp_socket_mock.hpp} (100%) create mode 100644 nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp create mode 100644 nebula_hw_interfaces/test_resources/hesai/at128.json create mode 100644 nebula_hw_interfaces/test_resources/hesai/ot128.json create mode 100644 nebula_hw_interfaces/test_resources/hesai/pandar40p.json create mode 100644 nebula_hw_interfaces/test_resources/hesai/pandar64.json create mode 100644 nebula_hw_interfaces/test_resources/hesai/qt128.json create mode 100644 nebula_hw_interfaces/test_resources/hesai/xt32.json diff --git a/nebula_hw_interfaces/CMakeLists.txt b/nebula_hw_interfaces/CMakeLists.txt index 13efaaa1..564439e9 100644 --- a/nebula_hw_interfaces/CMakeLists.txt +++ b/nebula_hw_interfaces/CMakeLists.txt @@ -101,6 +101,7 @@ if(BUILD_TESTING) find_package(ament_cmake_gtest REQUIRED) find_package(ament_cmake_gmock REQUIRED) + find_package(nlohmann_json) ament_add_gtest(test_udp test/common/test_udp.cpp @@ -120,10 +121,12 @@ if(BUILD_TESTING) ${nebula_hw_interfaces_hesai_INCLUDE_DIRS} ${boost_tcp_driver_INCLUDE_DIRS} ${boost_udp_driver_INCLUDE_DIRS} + ${nlohmann_json_INCLUDE_DIRS} include test) target_link_libraries(hesai_test_ptc + ${nlohmann_json_LIBRARIES} nebula_hw_interfaces_hesai ) endif() diff --git a/nebula_hw_interfaces/package.xml b/nebula_hw_interfaces/package.xml index 049df32b..48a7413f 100644 --- a/nebula_hw_interfaces/package.xml +++ b/nebula_hw_interfaces/package.xml @@ -23,6 +23,7 @@ ament_cmake_gmock ament_cmake_gtest ament_lint_auto + nlohmann-json-dev ament_cmake diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 34e3544e..831e581c 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -1,6 +1,8 @@ // Copyright 2024 TIER IV, Inc. #include "hesai/test_ptc/ptc_test.hpp" +#include "hesai/test_ptc/tcp_socket_mock.hpp" +#include "hesai/test_ptc/tcp_socket_replay.hpp" #include "nebula_common/hesai/hesai_common.hpp" #include "nebula_common/nebula_common.hpp" @@ -10,6 +12,7 @@ #include #include +#include namespace nebula::drivers { @@ -79,7 +82,7 @@ auto make_sensor_config(SensorModel model) TEST_P(PtcTest, ConnectionLifecycle) { /* Constructor does not immediately connect, destructor closes socket */ { - auto tcp_sock_ptr = make_mock_tcp_socket(); + auto tcp_sock_ptr = std::make_shared(); auto & tcp_sock = *tcp_sock_ptr; EXPECT_CALL(tcp_sock, close()).Times(AtLeast(1)); @@ -87,7 +90,7 @@ TEST_P(PtcTest, ConnectionLifecycle) } /* Full lifecycle without sending/receiving */ { - auto tcp_sock_ptr = make_mock_tcp_socket(); + auto tcp_sock_ptr = std::make_shared(); auto & tcp_sock = *tcp_sock_ptr; InSequence seq; @@ -104,6 +107,33 @@ TEST_P(PtcTest, ConnectionLifecycle) } } +TEST_P(PtcTest, PtcCommunication) +{ + const auto & model = GetParam(); + auto conversation_db = connections::parse_conversation_db("a"); + if (!conversation_db.has_value()) { + GTEST_SKIP() << "no conversation database found for " << model; + } + + connections::ReplayTcpSocket::ptc_handler_t cb = [&conversation_db]( + const auto & request, const auto & cb_header, + const auto & cb_payload, + const auto & cb_completion) { + if (conversation_db.find(request) == conversation_db.end()) { + throw "not"; + } + + const auto & responses = conversation_db[ptc_packet]; + + for (const message_t & response : responses) { + auto header = message_t(response.cbegin(), std::next(response.cbegin(), g_ptc_header_size)); + cb_header(header); + cb_payload(response); + cb_completion(); + } + }; +} + INSTANTIATE_TEST_SUITE_P(TestMain, PtcTest, testing::ValuesIn(g_models_under_test)); } // namespace nebula::drivers diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp index 0cbe4f96..23e195f2 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp @@ -14,7 +14,7 @@ #pragma once -#include "hesai/test_ptc/tcp_mock.hpp" +#include "hesai/test_ptc/tcp_socket_mock.hpp" #include "nebula_common/loggers/console_logger.hpp" #include "nebula_common/nebula_common.hpp" #include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp" @@ -33,8 +33,6 @@ class PtcTest : public ::testing::TestWithParam void TearDown() override {} - static auto make_mock_tcp_socket() { return std::make_shared(); } - static auto make_hw_interface(std::shared_ptr tcp_socket) { auto model = GetParam(); diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_mock.hpp similarity index 100% rename from nebula_hw_interfaces/test/hesai/test_ptc/tcp_mock.hpp rename to nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_mock.hpp diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp new file mode 100644 index 00000000..ac605844 --- /dev/null +++ b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp @@ -0,0 +1,157 @@ +// Copyright 2024 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "nebula_common/util/expected.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nebula::drivers::connections +{ + +using nlohmann::json; +using std::string_literals::operator""s; + +using message_t = std::vector; +using conversation_db_t = std::map>; + +class ParseError : public std::runtime_error +{ +public: + explicit ParseError(const std::string & msg) : std::runtime_error(msg) {} + + ParseError() : std::runtime_error("unknown format") {} +}; + +namespace impl +{ +const size_t g_ptc_header_size = 8; +const std::string_view g_hex_prefix = "0x"; + +inline util::expected parse_message(const std::string & json_str) +{ + if (std::string_view json_view = json_str; + !std::equal(json_view.cbegin(), g_hex_prefix.cbegin(), g_hex_prefix.cend())) { + return ParseError(); + } + + message_t result; + for (size_t i = g_hex_prefix.length(); i < json_str.length(); i += 2) { + std::string hex_pair = json_str.substr(i, 2); + uint8_t byte = strtoul(hex_pair.c_str(), nullptr, 16); + if (errno) { + return ParseError(strerror(errno)); + } + result.emplace_back(byte); + } + + return result; +} +}; // namespace impl + +inline util::expected parse_conversation_db( + const std::string & json_filename) +{ + std::ifstream ifs(json_filename); + if (!ifs.is_open()) { + return ParseError{"failed to open file: " + json_filename}; + } + + json raw_db = json::parse(ifs); + if (!raw_db.contains("rules") || !raw_db["rules"].is_array()) { + return ParseError{}; + } + + conversation_db_t result; + for (const auto & obj : raw_db["rules"]) { + if (!obj.contains("request") || !obj["request"].is_string()) { + return ParseError{}; + } + + if (!obj.contains("responses") || !obj["responses"].is_array()) { + return ParseError{}; + } + + auto request_exp = impl::parse_message(obj["request"]); + if (!request_exp.has_value()) { + return request_exp.error(); + } + + std::vector responses; + for (const auto & response : obj["responses"]) { + if (!response.is_string()) { + return ParseError{}; + } + + auto parsed_message_exp = impl::parse_message(obj.template get()); + if (!parsed_message_exp.has_value()) { + return parsed_message_exp.error(); + } + responses.emplace_back(parsed_message_exp.value()); + } + + result.insert({request_exp.value(), responses}); + } + + return result; +} + +class ReplayTcpSocket : public AbstractTcpSocket +{ +public: + using ptc_handler_t = std::function; + + explicit ReplayTcpSocket(ptc_handler_t ptc_handler) : ptc_handler_(std::move(ptc_handler)) {} + + void init( + const std::string & /* host_ip */, uint16_t /* host_port */, + const std::string & /* remote_ip */, uint16_t /* remote_port */) override + { + } + + void bind() override {} + + void close() override {} + + void async_ptc_request( + message_t & ptc_packet, header_callback_t cb_header, payload_callback_t cb_payload, + completion_callback_t cb_completion) override + { + ptc_handler_(ptc_packet, cb_header, cb_payload, cb_completion); + } + +private: + ptc_handler_t ptc_handler_; +}; + +} // namespace nebula::drivers::connections diff --git a/nebula_hw_interfaces/test_resources/hesai/at128.json b/nebula_hw_interfaces/test_resources/hesai/at128.json new file mode 100644 index 00000000..d15e3d9c --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/at128.json @@ -0,0 +1,46 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x47740700000000b8", + "0x415433384331353639463342433035310000323032332d31302d323400000000000002000000ce77332e35302e313500000000000000000042320000000000000000000000000000332e313062383330000000000000000078787878780000000000000000000000302e30302e3033340000000000000000332e30362e30333200000000000000006131393138306466000000000000000039646231396236390000000000000000ffff2800800101000000000000000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002f", + "0xc0a801c9ffffff00c0a80101ffffffff0940277e00c8010bb800000e10010100020000000001000000094200000000" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x477405000000b848", + "0xeeff0105800303010204000000000002cc6f040000e71b005251330000e71b0052513300cc6f04000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000088ffff802000000078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff0078000080dfffff2c8602002d7c02002e72020030680200315e020032540200344a02003540020036360200382c0200392202003a1802003c0e02003d0402003efa010040f0010041e6010042dc010044d2010045c8010046be010048b4010049aa01004aa001004c9601004d8c01004e82010050780100516e010052640100545a01005550010056460100583c0100593201005a2801005c1e01005d1401005f0a01006000010061f6000063ec000064e2000065d8000067ce000068c4000069ba00006bb000006ca600006d9c00006f92000070880000717e000073740000746a00007560000077560000784c0000794200007b3800007c2e00007d2400007f1a0000801000008106000083fcffff84f2ffff85e8ffff87deffff88d4ffff89caffff8bc0ffff8cb6ffff8dacffff8fa2ffff9098ffff918effff9384ffff947affff9570ffff9766ffff985cffff9952ffff9b48ffff9c3effff9d34ffff9f2affffa020ffffa116ffffa30cffffa402ffffa6f8feffa7eefeffa8e4feffaadafeffabd0feffacc6feffaebcfeffafb2feffb0a8feffb29efeffb394feffb48afeffb680feffb776feffb86cfeffba62feffbb58feffbc4efeffbe44feffbf3afeffc030feffc226feffc31cfeffc412feffc608feffc7fefdffc8f4fdffcaeafdffcbe0fdffccd6fdffceccfdffcfc2fdffd0b8fdffd2aefdffd3a4fdffd49afdffd690fdff00000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e", + "0x0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d800000000", + "0x0000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a0908", + "0x07060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc0000000000000000", + "0x00000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf000000000000000000000000000000000000000000000000001818181513100e0d0b090807060504020100fffdfcfbfaf8f6f3f1eeeae6e2ddd8d8d8000000000000000000000000000000000000000000000000001818181512100e0c0a090807060504020100fffefdfcfbf9f8f5f3f0ede9e5e0dcdcdc000000000000000000000000000000000000000000000000001111110f0c0b090807050404030202010100fffffefdfcfbf9f7f5f2efece8e3dfdfdf00000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000", + "0x000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e101113141414000000000000000000000000", + "0x00000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e1011131414140000000000000000000000000000000000000000", + "0x0000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707", + "0x060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e1011131414140000000000000000000000000000000000000000000000000007070706050404030303020202030303030404", + "0x0505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b", + "0x0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e10121313130000000000000000", + "0x00000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e101213131300000000000000000000000000000000", + "0x0000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000", + "0x000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e10121313130000000000000000000000000000000000000000000000000007070706050504", + "0x04030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e10111314141400000000000000000000000000000000000000000000000000070707060504040303030202020303030304040505060708090a0b0c0d0e1012131313000000000000000000000000000000000000000000000000000707070605050404030303030303040404050506060708090a0b0c0d0e0f1113141414000000000000000000000000000000000000000000000000000707070606050504040303030304040404050506070809090a0b0c0d0e1011131414140000000000000000fe0a7ab2636443b714f0610b57297851219d3388017ee9ef1d26685a6485e540" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x477409000000003a", + "0x0000003e00c800000c4c00000cda00000c5000000c9300000bb100000cb100000b3100000b98fffff06001010000005300003f37020000000000" + ] + } + ] +} diff --git a/nebula_hw_interfaces/test_resources/hesai/ot128.json b/nebula_hw_interfaces/test_resources/hesai/ot128.json new file mode 100644 index 00000000..d96b87d9 --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/ot128.json @@ -0,0 +1,61 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x47740700000000e44f5433344336353739353334433635370000323032332d30332d3238000000000000ec9f0d017299312e332e31390000000000000000000000000000000000000000000000000000312e332e313000000000000000000000312e332e313300000000000000000000b7692a01804f543132382d423031000000000000000000000000000000000000000000000001383635303633313800000000000000000000000035343434303734313738302e30312e30362e31302e303256312e302e3000000000000000000000313033363733300038363530363332303836353036333139000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002fc0a801c9ffffff00c0a80101ffffffff0940277e025801000000000000010100020010000000000000000000000000" + ] + }, + { + "request": "0x4774180000000003010000", + "responses": [ + "0x4774180000000000" + ] + }, + { + "request": "0x477424000000000403000101", + "responses": [ + "0x4774240000000000" + ] + }, + { + "request": "0x4774230000000000", + "responses": [ + "0x47742300000000050000000e10" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x4774050000000a3e", + "0x4368616e6e656c2c456c65766174696f6e2c417a696d7574680a312c31352e313333342c302e3135313836320a322c31332e343433362c302e3133303237360a332c31312e393333382c312e32383435340a342c31302e363435392c312e32363734370a352c392e39393532322c302e3136343530370a362c392e33313738372c302e3132383431320a372c382e36343931362c302e31313834350a382c372e39373035312c302e303933333231350a392c372e36323635332c312e33353930310a31302c372e32373937312c312e33333638350a31312c362e39333234342c312e33313237370a31322c362e353738372c312e32393230320a31332c362e32323635352c312e333630340a31342c352e38363935352c312e33333831390a31352c352e353134352c312e33313731370a31362c352e31353435332c312e32393732390a31372c342e37393135392c302e3136303435330a31382c342e34333032392c302e3133383531380a31392c342e30363439332c302e3131363037370a32302c332e37303737352c302e303934333138320a32312c332e33343538332c302e3134393334390a32322c322e39373637382c302e31323938370a32332c322e36313535362c302e31303736320a32342c322e323434342c302e303834383238360a32352c322e31323236372c2d332e31333935350a32362c322e30303430332c312e33353835360a32372c312e38383131382c342e35343636360a32382c312e373533342c2d332e30373233350a32392c312e36333533382c312e32393132320a33302c312e35313138332c342e35323830370a33312c312e33383531312c2d332e30383330330a33322c312e323731372c312e333331390a33332c312e31343736392c342e35373533370a33342c312e30323232312c2d332e31303533350a33352c302e3839383334392c312e33343431310a33362c302e3737373033362c342e35333236370a33372c302e3634323930392c2d332e31333032350a33382c302e3532323630352c312e33323231310a33392c302e3430313037392c342e36303439390a34302c302e3236393331372c2d332e30363535360a34312c302e31353334372c302e3134393935350a34322c302e303236373631372c322e39383832360a34332c2d302e303939383333312c2d312e353335370a34342c2d302e3232343334382c302e3132383730310a34352c2d302e3335303235372c322e39363930360a34362c2d302e3437333937312c2d312e34373033350a34372c2d302e3539363832322c302e3130373830330a34382c2d302e3731343131342c322e39353031310a34392c2d302e3834383734362c2d312e3439310a35302c2d302e3936363637342c302e3137343530320a35312c2d312e30393130342c322e39", + "0x383930330a35322c2d312e32313935352c2d312e35313437360a35332c2d312e33343632352c302e303931333631340a35342c2d312e34363932322c322e39363938320a35352c2d312e35393430322c2d312e34373739380a35362c2d312e37323430342c302e3136303435330a35372c2d312e38333833372c322e39343639370a35382c2d312e39373536362c2d312e34393738340a35392c2d322e30393533382c302e3133383237380a36302c2d322e32313834392c332e30313930320a36312c2d322e33333930332c2d312e35323433310a36322c2d322e34363938312c302e3131353739390a36332c2d322e35393330392c322e393431390a36342c2d322e37323231352c2d312e34353330360a36352c2d322e38343234342c312e33373739350a36362c2d322e39373232362c342e36323535380a36372c2d332e30393734382c2d332e31353930370a36382c2d332e32323236322c312e33373638370a36392c2d332e33343835362c342e36303837350a37302c2d332e34373239352c2d332e30393136380a37312c2d332e35383434342c312e33333830360a37322c2d332e373231382c342e35383737370a37332c2d332e38353231332c2d332e31313534350a37342c2d332e39373533342c312e34303538370a37352c2d342e30393935352c342e36323733350a37362c2d342e32323538312c2d332e31343035370a37372c2d342e33353333312c312e33323731330a37382c2d342e34383033332c342e36313033350a37392c2d342e36303436312c2d332e31303439370a38302c2d342e373235362c312e33393634390a38312c2d342e38343335382c342e35383934330a38322c2d342e39373931342c2d332e31323736320a38332c2d352e31303333372c312e33373335340a38342c2d352e32323339392c342e36353933390a38352c2d352e33333135332c2d332e31353238330a38362c2d352e34383239322c312e333533310a38372c2d352e36303339372c342e35383031380a38382c2d352e37323932382c2d332e30383633310a38392c2d352e38343936332c302e3137363439360a39302c2d362e32323833362c302e3135373036350a39312c2d362e363130332c302e3133343234380a39322c2d362e39363939382c302e3130393334370a39332c2d372e33363135322c302e3138323230340a39342c2d372e37333937322c302e3135393734320a39352c2d382e31313036332c302e3133383331370a39362c2d382e34383937342c302e3131313735380a39372c2d382e38353932332c312e343039310a39382c2d392e32333631322c312e33383839380a39392c2d392e36303230312c312e33363631350a3130302c2d392e39383034312c312e333432340a3130312c2d31302e333534372c312e34313234", + "0x360a3130322c2d31302e373139382c312e33383830320a3130332c2d31312e303933342c312e33363432350a3130342c2d31312e343638382c312e33343036330a3130352c2d31312e383236332c302e3137303432380a3130362c2d31322e322c302e3135313430350a3130372c2d31322e353730372c302e3133303733360a3130382c2d31322e393332372c302e3130363336370a3130392c2d31332e323936362c302e3138363730320a3131302c2d31332e363631342c302e3136323034320a3131312c2d31342e303232322c302e3134313332380a3131322c2d31342e333833392c302e3131353431330a3131332c2d31342e373430332c312e34323536310a3131342c2d31352e313032352c312e34303731360a3131352c2d31352e343533322c312e333832370a3131362c2d31352e3831362c312e33363037340a3131372c2d31362e313637312c312e34323932350a3131382c2d31362e353132362c312e34303532310a3131392c2d31362e383637362c312e333831370a3132302c2d31372e323136362c312e33363032330a3132312c2d31372e3535382c302e3139323539340a3132322c2d31382e323437342c302e3137343236390a3132332c2d31382e393230362c302e3135343333360a3132342c2d31392e353932392c302e3133333033350a3132352c2d32302e323339312c312e343338320a3132362c2d32312e353238362c312e34303630360a3132372c2d32322e393130322c302e3134353432310a3132382c2d32342e363336372c302e31363234390a" + ] + }, + { + "request": "0x47742200000000050000000e10", + "responses": [ + "0x4774220000000000" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x47740900000000360000004c025700000b3800000b0400000b3600000adc00000afa00000b4a00000a9600000b7200000000010f001b00cf02000000ab00" + ] + }, + { + "request": "0x4774270000000000", + "responses": [ + "0x47742700000000400002d1ca000004590000080800000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + } + ] +} diff --git a/nebula_hw_interfaces/test_resources/hesai/pandar40p.json b/nebula_hw_interfaces/test_resources/hesai/pandar40p.json new file mode 100644 index 00000000..247883ea --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/pandar40p.json @@ -0,0 +1,63 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x4774070000000078", + "0x504134303333434335373938333343443534323032302d31312d3236000000000000ec9f0d006062322e31302e3400000000000000000000312e302e300000000000000000000000342e3531000000000000000000000000342e332e3430612000000000000000008c800000280000000000000000000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002f", + "0xc0a801c9ffffff00c0a80101ffffffff0940277e025801000000000e10010001020000000001000000000000000000" + ] + }, + { + "request": "0x4774180000000003010000", + "responses": [ + "0x4774180000000000" + ] + }, + { + "request": "0x47741f000000000101", + "responses": [ + "0x47741f0000000000" + ] + }, + { + "request": "0x4774240000000006000000010100", + "responses": [ + "0x4774240000000000" + ] + }, + { + "request": "0x4774230000000000", + "responses": [ + "0x4774230000000005", + "0x0000000e10" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x47740500000002ae", + "0x4c617365722069642c456c65766174696f6e2c417a696d7574680a312c31342e3734362c2d312e3034320a322c31302e3839362c2d312e3034320a332c372e3932332c2d312e3034320a342c342e3932312c2d312e3034320a352c322e3930342c2d312e3034320a362c312e3839322c2d312e3034320a372c312e3535322c332e3132350a382c312e3231352c2d352e3230380a392c302e3837372c2d312e3034320a31302c302e3533392c332e3132350a31312c302e3230312c2d352e3230380a31322c2d302e3133362c2d312e3034320a31332c2d302e3437332c332e3132350a31342c2d302e3831312c2d352e3230380a31352c2d312e3134392c2d312e3034320a31362c2d312e3438372c332e3132350a31372c2d312e3832342c2d352e3230380a31382c2d322e3136342c2d312e3034320a31392c2d322e3530312c332e3132350a32302c2d322e3833362c2d352e3230380a32312c2d332e3137362c2d312e3034320a32322c2d332e3531312c332e3132350a32332c2d332e3834382c2d352e3230380a32342c2d342e3138362c2d312e3034320a32352c2d342e3532312c332e3132350a32362c2d342e3835362c2d352e3230380a32372c2d352e3139332c2d312e3034320a32382c2d352e3532372c332e3132350a32392c2d352e3836322c2d352e3230380a33302c2d362e3139372c2d312e3034320a33312c2d372e3139392c2d312e3034320a33322c2d382e3139352c2d312e3034320a33332c2d392e3139362c2d312e3034320a33342c2d31302e3138312c2d312e3034320a33352c2d31312e3136382c2d312e3034320a33362c2d31322e3134322c2d312e3034320a33372c2d31332e31312c2d312e3034320a33382c2d31342e3036362c2d312e3034320a33392c2d31392e3032352c2d312e3034320a34302c2d32352e3033332c2d312e303432" + ] + }, + { + "request": "0x47742200000000050000000e10", + "responses": [ + "0x4774220000000000" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x4774090000000036", + "0x00000044025800000b8400000ba700000a7d00000a6100000a7200000b4200000ca500000c4600000000008b00003fba020000000000" + ] + } + ] +} diff --git a/nebula_hw_interfaces/test_resources/hesai/pandar64.json b/nebula_hw_interfaces/test_resources/hesai/pandar64.json new file mode 100644 index 00000000..9bcb76d4 --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/pandar64.json @@ -0,0 +1,56 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x4774070000000079", + "0x504136343346434135373932334643413533323031392d30342d31300a0000000000ec9f0d0034e8322e372e370000000000000000000000312e302e300000000000000000000000352e313000000000000000000000000076342e332e31356200000000000000008c7d020000000000000000000000000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002f", + "0xc0a801c9ffffff00c0a80101ffffffff0940277e025801232800008ca0010000000000000000000000000000000000" + ] + }, + { + "request": "0x47741e000000000102", + "responses": [ + "0x47741e0000000000" + ] + }, + { + "request": "0x4774180000000003010000", + "responses": [ + "0x4774180000000000" + ] + }, + { + "request": "0x47741f000000000101", + "responses": [ + "0x47741f0000000000" + ] + }, + { + "request": "0x4774240000000006000000010100", + "responses": [ + "0x4774240000000000" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x4774050000000428", + "0x4c617365722069642c456c65766174696f6e2c417a696d7574680a312c31342e3934332c2d312e3034320a322c31312e3039332c2d312e3034320a332c382e31322c2d312e3034320a342c352e3131382c2d312e3034320a352c332e3130312c2d312e3034320a362c322e3038392c2d312e3034320a372c312e3932312c312e3034320a382c312e3734392c332e3132350a392c312e3538332c352e3230380a31302c312e3431322c2d352e3230380a31312c312e3234352c2d332e3132350a31322c312e3037342c2d312e3034320a31332c302e3930372c312e3034320a31342c302e3733362c332e3132350a31352c302e3536392c352e3230380a31362c302e3339382c2d352e3230380a31372c302e32332c2d332e3132350a31382c302e3036312c2d312e3034320a31392c2d302e3130382c312e3034320a32302c2d302e3237362c332e3132350a32312c2d302e3434372c352e3230380a32322c2d302e3631342c2d352e3230380a32332c2d302e3738342c2d332e3132350a32342c2d302e3935322c2d312e3034320a32352c2d312e3132332c312e3034320a32362c2d312e32392c332e3132350a32372c2d312e3436312c352e3230380a32382c2d312e3632372c2d352e3230380a32392c2d312e3739392c2d332e3132350a33302c2d312e3936372c2d312e3034320a33312c2d322e3133372c312e3034320a33322c2d322e3330342c332e3132350a33332c2d322e3437352c352e3230380a33342c2d322e3633392c2d352e3230380a33352c2d322e3831322c2d332e3132350a33362c2d322e3937392c2d312e3034320a33372c2d332e3134392c312e3034320a33382c2d332e3331342c332e3132350a33392c2d332e3438372c352e3230380a34302c2d332e3635312c2d352e3230380a34312c2d332e3832332c2d332e3132350a34322c2d332e3938392c2d312e3034320a34332c2d342e31362c312e3034320a34342c2d342e3332342c332e3132350a34352c2d342e3439372c352e3230380a34362c2d342e3635392c2d352e3230380a34372c2d342e3833312c2d332e3132350a34382c2d342e3939362c2d312e3034320a34392c2d352e3136382c312e3034320a35302c2d352e33332c332e3132350a35312c2d352e3530342c352e3230380a35322c2d352e3636352c2d352e3230380a35332c2d352e3833372c2d332e3132350a35342c2d362e302c2d312e3034320a35352c2d372e3030322c2d312e3034320a35362c2d372e3939382c2d312e3034320a35372c2d382e3939392c2d312e3034320a35382c2d392e3938342c2d312e3034320a35392c2d31302e3937312c2d312e3034320a36302c2d31312e3934352c2d312e3034320a36312c2d31322e3931332c2d312e3034320a36322c2d31332e3836392c2d312e3034320a36332c2d31382e3832382c2d312e3034320a36342c2d32342e3833362c2d312e303432" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x4774090000000036", + "0x000000f1025800000fac00000fd600000f3300000faf00000f3600000fdb000011910000111900000000000000000000000000000000" + ] + } + ] +} diff --git a/nebula_hw_interfaces/test_resources/hesai/qt128.json b/nebula_hw_interfaces/test_resources/hesai/qt128.json new file mode 100644 index 00000000..0bf0cca2 --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/qt128.json @@ -0,0 +1,63 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x4774070000000090", + "0x515433454344353639353345434635340000323032332d30382d3231000000000000ec9f0d016ab8332e312e363600000000000000000000312e302e300000000000000000000000332e312e333600000000000000000000332e312e3430000000000000000000000f1d7bda20018051543132384332582d453031000000000000000000000000000000000000000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002f", + "0xc0a801c9ffffff00c0a80101ffffffff0940277e025800000000000e10010000010010000001000000000000000000" + ] + }, + { + "request": "0x47741e000000000102", + "responses": [ + "0x47741e0000000000" + ] + }, + { + "request": "0x4774180000000003010000", + "responses": [ + "0x4774180000000000" + ] + }, + { + "request": "0x4774240000000006000000010100", + "responses": [ + "0x4774240000000000" + ] + }, + { + "request": "0x4774230000000000", + "responses": [ + "0x4774230000000005", + "0x0000000e10" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x47740500000008d7", + "0x454546462c312c310a4c617365722069642c456c65766174696f6e2c417a696d7574680a312c2d35332e3431372c392e3439380a322c2d35312e3831382c392e3131340a332c2d35302e3230392c382e3739310a342c2d34382e3736352c382e3532300a352c2d34372e3336382c382e3234340a362c2d34362e3032342c382e3031350a372c2d34342e3639352c372e3830320a382c2d34332e3434372c372e3632310a392c2d34322e3230362c372e3435350a31302c2d34312e3032382c372e3330380a31312c2d33392e3834332c372e3137320a31322c2d33382e3732332c372e3035340a31332c2d33372e3539322c362e3934340a31342c2d33362e3532392c362e3835320a31352c2d33352e3435352c362e3733360a31362c2d33342e3432392c362e3635370a31372c2d33332e3338392c362e3535380a31382c2d33322e3430322c362e3439320a31392c2d33312e3339312c362e3433300a32302c2d33302e3433322c362e3335370a32312c2d32392e3435382c362e3238360a32322c2d32382e3531372c362e3233380a32332c2d32372e3537322c362e3136380a32342c2d32362e3635352c362e3133320a32352c2d32352e3732322c362e3037310a32362c2d32342e3832312c362e3033300a32372c2d32332e3930392c352e3938390a32382c2d32332e3032342c352e3934370a32392c2d32322e3132322c352e3930370a33302c2d32312e3236312c352e3836370a33312c2d32302e3338302c352e3832330a33322c2d31392e3533322c352e3830340a33332c2d31382e3639312c2d362e3432370a33342c2d31372e3835332c2d362e3339310a33352c2d31362e3938372c2d362e3335370a33362c2d31362e3135362c2d362e3333320a33372c2d31352e3330302c2d362e3239360a33382c2d31342e3438302c2d362e3236370a33392c2d31332e3633342c2d362e3235300a34302c2d31322e3831372c2d362e3232390a34312c2d31312e3937322c2d362e3231300a34322c2d31312e3136332c2d362e3138370a34332c2d31302e3332372c2d362e3136370a34342c2d392e3532302c2d362e3134380a34352c2d382e3638382c2d362e3133370a34362c2d372e3838382c2d362e3132300a34372c2d372e3036312c2d362e3131380a34382c2d362e3236362c2d362e3039390a34392c2d352e3434322c2d362e3130360a35302c2d342e3634332c2d362e3039300a35312c2d332e3831372c2d362e3037370a35322c2d332e3032342c2d362e3037330a35332c2d322e3230342c2d362e3037300a35342c2d312e3431342c2d362e3035390a35352c2d302e3538372c2d362e3037320a35362c302e3230342c2d362e3036330a35372c312e3032372c2d362e3036360a35382c312e3831332c2d362e3036360a35392c322e3633352c2d362e3037330a36302c332e3433312c2d362e3036380a36312c342e3235332c2d362e3038330a36322c352e3035312c2d362e3038350a36332c352e3837332c2d362e3130340a36342c362e3636372c2d362e3130350a36352c372e3535302c352e3532380a36362c382e3334372c352e3534330a36372c392e3138322c352e3535300a36382c392e3938322c352e3536350a36392c31302e3831392c352e3537360a37302c31312e3632382c352e3630310a37312c31322e3436372c352e3631330a37322c31332e3237392c352e3633340a37332c31342e3132342c352e3635340a37342c31342e3934372c352e3637390a37352c31352e3739372c352e3639390a37362c31362e3633312c352e3732370a37372c31372e3439322c352e3736360a37382c31382e3332342c352e3739330a37392c31392e3139392c352e3831360a38302c32302e3034332c352e3835330a38312c32302e3933342c352e3838360a38322c32312e3739332c352e3932310a38332c32322e3638372c352e3935330a38342c32332e3536312c352e3939360a38352c32342e3437382c362e3033370a38362c32352e3336362c362e3038360a38372c32362e3239372c362e3133370a38382c32372e3230322c362e3139300a38392c32382e3135362c362e3234310a39302c32392e3038332c362e3239380a39312c33302e3036302c362e3335360a39322c33312e3031342c362e3432340a39332c33322e3031392c362e3439390a39342c33322e3939372c362e3537370a39352c33342e3033322c362e3636300a39362c33352e3033362c362e3734360a39372c31382e3732372c2d362e3430390a39382c31392e3537312c2d362e3434310a39392c32302e3435302c2d362e3438340a3130302c32312e3330392c2d362e3531380a3130312c32322e3230352c2d362e3537340a3130322c32332e3037362c2d362e3538390a3130332c32332e3939322c2d362e3634330a3130342c32342e3836392c2d362e3638370a3130352c32352e3739352c2d362e3734390a3130362c32362e3730312c2d362e3830320a3130372c32372e3634302c2d362e3833300a3130382c32382e3536382c2d362e3838390a3130392c32392e3533392c2d362e3935350a3131302c33302e3438302c2d372e3032310a3131312c33312e3437362c2d372e3131300a3131322c33322e3434312c2d372e3138350a3131332c33332e3436392c2d372e3238330a3131342c33342e3437322c2d372e3337380a3131352c33352e3533302c2d372e3439300a3131362c33362e3537322c2d372e3539340a3131372c33372e3636382c2d372e3731390a3131382c33382e3735372c2d372e3833370a3131392c33392e3931322c2d372e3938380a3132302c34312e3035332c2d382e3132350a3132312c34322e3235392c2d382e3332300a3132322c34332e3436392c2d382e3530350a3132332c34342e3736302c2d382e3731360a3132342c34362e3034382c2d382e3933310a3132352c34372e3434302c2d392e3139310a3132362c34382e3833352c2d392e3437310a3132372c35302e3333302c2d392e3739340a3132382c35312e3835322c2d31302e3136300a33323631343731346130353663316166666139653032623632623265623837303334393531646532343334323064353338383239336235636130613763373464" + ] + }, + { + "request": "0x47742200000000050000000e10", + "responses": [ + "0x4774220000000000" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x477409000000003a", + "0x000005af025800000dec00000d8300000f0200000eff00000f0500000e2900000e7700000f0500000f0e00000000000400000026020000000000" + ] + } + ] +} diff --git a/nebula_hw_interfaces/test_resources/hesai/xt32.json b/nebula_hw_interfaces/test_resources/hesai/xt32.json new file mode 100644 index 00000000..3311e375 --- /dev/null +++ b/nebula_hw_interfaces/test_resources/hesai/xt32.json @@ -0,0 +1,70 @@ +{ + "rules": [ + { + "request": "0x4774070000000000", + "responses": [ + "0x4774070000000090", + "0x585433304346353039463330434535350000323032322d31312d3238000000000000ec9f0d012d2a312e302e313000000000000000000000312e302e300000000000000000000000312e322e370000000000000000000000202020312e352e37000000000000000023191900200000000000000000000000000000000000000000000000000000000000000000000000" + ] + }, + { + "request": "0x4774080000000000", + "responses": [ + "0x477408000000002f", + "0xc0a801c9ffffff00c0a80101ffffffff0940277e025801000000000e10010100020000000000020000000000000000" + ] + }, + { + "request": "0x4774180000000003010000", + "responses": [ + "0x4774180000000000" + ] + }, + { + "request": "0x47741f000000000101", + "responses": [ + "0x47741f0000000000" + ] + }, + { + "request": "0x4774240000000006000000010100", + "responses": [ + "0x4774240000000000" + ] + }, + { + "request": "0x4774230000000000", + "responses": [ + "0x4774230000000005", + "0x0000000e10" + ] + }, + { + "request": "0x4774050000000000", + "responses": [ + "0x477405000000020f", + "0x4c617365722069642c456c65766174696f6e2c417a696d7574680a312c31342e3937342c302e3638320a322c31332e3935362c302e3638330a332c31322e3934342c302e3638330a342c31312e3933392c302e3637360a352c31302e3933302c302e3637360a362c392e3932352c302e3637340a372c382e3932312c302e3637330a382c372e3932352c302e3637330a392c362e3932342c302e3637310a31302c352e3932362c302e3637300a31312c342e3932352c302e3636370a31322c332e3933312c302e3636350a31332c322e3933322c302e3636310a31342c312e3934312c302e3636310a31352c302e3934362c302e3635390a31362c2d302e3034362c302e3635360a31372c2d312e3034342c302e3635330a31382c2d322e3034302c302e3634390a31392c2d332e3034312c302e3634360a32302c2d342e3033362c302e3634320a32312c2d352e3033322c302e3633370a32322c2d362e3033312c302e3633310a32332c2d372e3033302c302e3632360a32342c2d382e3032392c302e3632310a32352c2d392e3032392c302e3631350a32362c2d31302e3033302c302e3630370a32372c2d31312e3033322c302e3630310a32382c2d31322e3033362c302e3539330a32392c2d31332e3034322c302e3538350a33302c2d31342e3035302c302e3537340a33312c2d31352e3036352c302e3536370a33322c2d31362e3038362c302e3535370a" + ] + }, + { + "request": "0x47742200000000050000000e10", + "responses": [ + "0x4774220000000000" + ] + }, + { + "request": "0x4774090000000000", + "responses": [ + "0x4774090000000036", + "0x000001e3025700000cc600000d6600000cbc00000d0200000d2a00000d5200000d2a000000000000000000bf000077c7020000000000" + ] + }, + { + "request": "0x4774270000000000", + "responses": [ + "0x4774270000000040", + "0x000002ae00000424b6fa4ae000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + } + ] +} From 2f6e8cdae366bc6b31518f17af56c540a279f0c1 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Wed, 27 Nov 2024 18:29:14 +0900 Subject: [PATCH 28/36] WIP Signed-off-by: Max SCHMELLER --- nebula_hw_interfaces/CMakeLists.txt | 2 + nebula_hw_interfaces/test/hesai/test_ptc.cpp | 128 ++++++++++++++++-- .../test/hesai/test_ptc/ptc_test.hpp | 6 +- .../test/hesai/test_ptc/tcp_socket_replay.hpp | 24 ++-- .../hesai/{ot128.json => Pandar128E4X.json} | 0 .../hesai/{pandar40p.json => Pandar40P.json} | 0 .../hesai/{pandar64.json => Pandar64.json} | 0 .../hesai/{at128.json => PandarAT128.json} | 0 .../hesai/{qt128.json => PandarQT128.json} | 0 .../hesai/{xt32.json => PandarXT32.json} | 0 10 files changed, 131 insertions(+), 29 deletions(-) rename nebula_hw_interfaces/test_resources/hesai/{ot128.json => Pandar128E4X.json} (100%) rename nebula_hw_interfaces/test_resources/hesai/{pandar40p.json => Pandar40P.json} (100%) rename nebula_hw_interfaces/test_resources/hesai/{pandar64.json => Pandar64.json} (100%) rename nebula_hw_interfaces/test_resources/hesai/{at128.json => PandarAT128.json} (100%) rename nebula_hw_interfaces/test_resources/hesai/{qt128.json => PandarQT128.json} (100%) rename nebula_hw_interfaces/test_resources/hesai/{xt32.json => PandarXT32.json} (100%) diff --git a/nebula_hw_interfaces/CMakeLists.txt b/nebula_hw_interfaces/CMakeLists.txt index 564439e9..42b19639 100644 --- a/nebula_hw_interfaces/CMakeLists.txt +++ b/nebula_hw_interfaces/CMakeLists.txt @@ -103,6 +103,8 @@ if(BUILD_TESTING) find_package(ament_cmake_gmock REQUIRED) find_package(nlohmann_json) + add_definitions(-D_TEST_RESOURCES_PATH="${PROJECT_SOURCE_DIR}/test_resources/") + ament_add_gtest(test_udp test/common/test_udp.cpp ) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 831e581c..dc4b789b 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -5,6 +5,8 @@ #include "hesai/test_ptc/tcp_socket_replay.hpp" #include "nebula_common/hesai/hesai_common.hpp" #include "nebula_common/nebula_common.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_cmd_response.hpp" #include #include @@ -12,7 +14,31 @@ #include #include +#include +#include +#include #include +#include + +#ifndef _TEST_RESOURCES_PATH +static_assert(false, "No test resources path defined"); +#endif + +#define GTEST_SKIP_PRINT(x) \ + do { \ + std::cout << x << '\n'; \ + GTEST_SKIP() << x; \ + } while (0) + +#define ASSERT_NO_THROW_PRINT(expr) \ + do { \ + try { \ + expr; \ + } catch (const std::exception & e) { \ + std::cout << e.what() << '\n'; \ + ASSERT_NO_THROW(throw e); \ + } \ + } while (0) namespace nebula::drivers { @@ -30,6 +56,7 @@ const SensorModel g_models_under_test[] = { const uint16_t g_u16_invalid = 0x4242; const uint16_t g_ptc_port = 9347; +const size_t g_ptc_header_size = 8; const char g_host_ip[] = "192.168.42.42"; const char g_sensor_ip[] = "192.168.84.84"; @@ -110,28 +137,103 @@ TEST_P(PtcTest, ConnectionLifecycle) TEST_P(PtcTest, PtcCommunication) { const auto & model = GetParam(); - auto conversation_db = connections::parse_conversation_db("a"); - if (!conversation_db.has_value()) { - GTEST_SKIP() << "no conversation database found for " << model; + + // //////////////////////////////////////// + // Set up database-based replay TCP socket + // //////////////////////////////////////// + + using ptc_handler_t = connections::ReplayTcpSocket::ptc_handler_t; + using header_callback_t = connections::ReplayTcpSocket::header_callback_t; + using payload_callback_t = connections::ReplayTcpSocket::payload_callback_t; + using completion_callback_t = connections::ReplayTcpSocket::completion_callback_t; + using connections::message_t; + + auto conversation_db_path = std::filesystem::path(_TEST_RESOURCES_PATH) / "hesai" / + (sensor_model_to_string(model) + ".json"); + if (!std::filesystem::exists(conversation_db_path)) { + GTEST_SKIP_PRINT("conversation DB " << conversation_db_path << " does not exist"); + } + + auto conversation_db_exp = connections::parse_conversation_db(conversation_db_path); + if (!conversation_db_exp.has_value()) { + std::cout << "ParseError: " << conversation_db_exp.error().what() << '\n'; + GTEST_SKIP_PRINT( + "conversation DB for model " + << model << " could not be parsed due to ParseError: " << conversation_db_exp.error().what()); } - connections::ReplayTcpSocket::ptc_handler_t cb = [&conversation_db]( - const auto & request, const auto & cb_header, - const auto & cb_payload, - const auto & cb_completion) { + connections::conversation_db_t conversation_db = conversation_db_exp.value(); + + ptc_handler_t cb = [&conversation_db]( + const message_t & request, const header_callback_t & cb_header, + const payload_callback_t & cb_payload, + const completion_callback_t & cb_completion) { if (conversation_db.find(request) == conversation_db.end()) { - throw "not"; + std::stringstream ss; + ss << "0x"; + for (const uint8_t & byte : request) { + ss << std::hex << std::setfill('0') << std::setw(2) << static_cast(byte); + } + GTEST_SKIP_PRINT("request " << ss.str() << " not found in conversation DB"); } - const auto & responses = conversation_db[ptc_packet]; + const auto & responses = conversation_db[request]; + + message_t complete_response; for (const message_t & response : responses) { - auto header = message_t(response.cbegin(), std::next(response.cbegin(), g_ptc_header_size)); - cb_header(header); - cb_payload(response); - cb_completion(); + complete_response.insert(complete_response.end(), response.cbegin(), response.cend()); } + + auto header = message_t( + complete_response.cbegin(), std::next(complete_response.cbegin(), g_ptc_header_size)); + cb_header(header); + cb_payload(complete_response); + cb_completion(); }; + + auto tcp_sock_ptr = std::make_shared(std::move(cb)); + + // //////////////////////////////////////// + // Test HW interface + // //////////////////////////////////////// + + auto hw_interface = make_hw_interface(tcp_sock_ptr); + + auto cfg = make_sensor_config(GetParam()); + hw_interface->SetSensorConfiguration(cfg); + hw_interface->InitializeTcpDriver(); + + // //////////////////////////////////////// + // Applicable to all models + // //////////////////////////////////////// + + std::shared_ptr config; + ASSERT_NO_THROW_PRINT(config = hw_interface->GetConfig()); + ASSERT_NE(config, nullptr); + + std::shared_ptr inventory; + ASSERT_NO_THROW_PRINT(inventory = hw_interface->GetInventory()); + ASSERT_NE(inventory, nullptr); + + std::vector calibration; + ASSERT_NO_THROW_PRINT(calibration = hw_interface->GetLidarCalibrationBytes()); + ASSERT_FALSE(calibration.empty()); + + std::shared_ptr status; + ASSERT_NO_THROW_PRINT(status = hw_interface->GetLidarStatus()); + ASSERT_NE(status, nullptr); + + switch (model) { + case SensorModel::HESAI_PANDARQT128: + case SensorModel::HESAI_PANDARXT32: + case SensorModel::HESAI_PANDARAT128: + case SensorModel::HESAI_PANDAR128_E4X: + ASSERT_NO_THROW_PRINT(hw_interface->GetPtpConfig()); + ASSERT_NO_THROW_PRINT(hw_interface->GetPtpDiagStatus()); + default: + break; + } } INSTANTIATE_TEST_SUITE_P(TestMain, PtcTest, testing::ValuesIn(g_models_under_test)); diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp index 23e195f2..3076712f 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc/ptc_test.hpp @@ -14,9 +14,9 @@ #pragma once -#include "hesai/test_ptc/tcp_socket_mock.hpp" #include "nebula_common/loggers/console_logger.hpp" #include "nebula_common/nebula_common.hpp" +#include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/connections/tcp.hpp" #include "nebula_hw_interfaces/nebula_hw_interfaces_hesai/hesai_hw_interface.hpp" #include @@ -29,11 +29,11 @@ namespace nebula::drivers class PtcTest : public ::testing::TestWithParam { protected: - void SetUp() override {} + void SetUp() override { std::cout << "GetParam() = " << GetParam() << '\n'; } void TearDown() override {} - static auto make_hw_interface(std::shared_ptr tcp_socket) + static auto make_hw_interface(std::shared_ptr tcp_socket) { auto model = GetParam(); diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp index ac605844..0e706a21 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp @@ -21,12 +21,11 @@ #include #include +#include #include #include -#include #include #include -#include #include #include #include @@ -47,20 +46,17 @@ class ParseError : public std::runtime_error { public: explicit ParseError(const std::string & msg) : std::runtime_error(msg) {} - - ParseError() : std::runtime_error("unknown format") {} }; namespace impl { -const size_t g_ptc_header_size = 8; const std::string_view g_hex_prefix = "0x"; inline util::expected parse_message(const std::string & json_str) { if (std::string_view json_view = json_str; - !std::equal(json_view.cbegin(), g_hex_prefix.cbegin(), g_hex_prefix.cend())) { - return ParseError(); + !std::equal(g_hex_prefix.cbegin(), g_hex_prefix.cend(), json_view.cbegin())) { + return ParseError{"hex string not starting with expected "s + std::string(g_hex_prefix)}; } message_t result; @@ -68,7 +64,9 @@ inline util::expected parse_message(const std::string & j std::string hex_pair = json_str.substr(i, 2); uint8_t byte = strtoul(hex_pair.c_str(), nullptr, 16); if (errno) { - return ParseError(strerror(errno)); + std::array strerror_buf{}; + strerror_r(errno, strerror_buf.data(), strerror_buf.size()); + return ParseError(std::string(strerror_buf.begin())); } result.emplace_back(byte); } @@ -87,17 +85,17 @@ inline util::expected parse_conversation_db( json raw_db = json::parse(ifs); if (!raw_db.contains("rules") || !raw_db["rules"].is_array()) { - return ParseError{}; + return ParseError{"key rules not found or not an array"}; } conversation_db_t result; for (const auto & obj : raw_db["rules"]) { if (!obj.contains("request") || !obj["request"].is_string()) { - return ParseError{}; + return ParseError{"key request not found or not a string"}; } if (!obj.contains("responses") || !obj["responses"].is_array()) { - return ParseError{}; + return ParseError{"key responses not found or not an array"}; } auto request_exp = impl::parse_message(obj["request"]); @@ -108,10 +106,10 @@ inline util::expected parse_conversation_db( std::vector responses; for (const auto & response : obj["responses"]) { if (!response.is_string()) { - return ParseError{}; + return ParseError{"response is not a string"}; } - auto parsed_message_exp = impl::parse_message(obj.template get()); + auto parsed_message_exp = impl::parse_message(response.template get()); if (!parsed_message_exp.has_value()) { return parsed_message_exp.error(); } diff --git a/nebula_hw_interfaces/test_resources/hesai/ot128.json b/nebula_hw_interfaces/test_resources/hesai/Pandar128E4X.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/ot128.json rename to nebula_hw_interfaces/test_resources/hesai/Pandar128E4X.json diff --git a/nebula_hw_interfaces/test_resources/hesai/pandar40p.json b/nebula_hw_interfaces/test_resources/hesai/Pandar40P.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/pandar40p.json rename to nebula_hw_interfaces/test_resources/hesai/Pandar40P.json diff --git a/nebula_hw_interfaces/test_resources/hesai/pandar64.json b/nebula_hw_interfaces/test_resources/hesai/Pandar64.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/pandar64.json rename to nebula_hw_interfaces/test_resources/hesai/Pandar64.json diff --git a/nebula_hw_interfaces/test_resources/hesai/at128.json b/nebula_hw_interfaces/test_resources/hesai/PandarAT128.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/at128.json rename to nebula_hw_interfaces/test_resources/hesai/PandarAT128.json diff --git a/nebula_hw_interfaces/test_resources/hesai/qt128.json b/nebula_hw_interfaces/test_resources/hesai/PandarQT128.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/qt128.json rename to nebula_hw_interfaces/test_resources/hesai/PandarQT128.json diff --git a/nebula_hw_interfaces/test_resources/hesai/xt32.json b/nebula_hw_interfaces/test_resources/hesai/PandarXT32.json similarity index 100% rename from nebula_hw_interfaces/test_resources/hesai/xt32.json rename to nebula_hw_interfaces/test_resources/hesai/PandarXT32.json From f436ae4323a03a94691c4c9fea743f3d39d8a0f4 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Wed, 27 Nov 2024 19:01:16 +0900 Subject: [PATCH 29/36] test(hesai): fix hex parsing Signed-off-by: Max SCHMELLER --- .../test/hesai/test_ptc/tcp_socket_replay.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp index 0e706a21..bbd6f07a 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc/tcp_socket_replay.hpp @@ -62,11 +62,10 @@ inline util::expected parse_message(const std::string & j message_t result; for (size_t i = g_hex_prefix.length(); i < json_str.length(); i += 2) { std::string hex_pair = json_str.substr(i, 2); - uint8_t byte = strtoul(hex_pair.c_str(), nullptr, 16); - if (errno) { - std::array strerror_buf{}; - strerror_r(errno, strerror_buf.data(), strerror_buf.size()); - return ParseError(std::string(strerror_buf.begin())); + char * endptr = nullptr; + uint8_t byte = strtoul(hex_pair.c_str(), &endptr, 16); + if (endptr != hex_pair.c_str() + 2) { + return ParseError{"String '" + hex_pair + "' is not a valid hex pair"}; } result.emplace_back(byte); } From d2db7737e3fd27dc811bcc6fd14e3c3769dc6cf5 Mon Sep 17 00:00:00 2001 From: Max SCHMELLER Date: Wed, 27 Nov 2024 19:01:38 +0900 Subject: [PATCH 30/36] test(hesai): remove unrecorded commands from test suite Signed-off-by: Max SCHMELLER --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index dc4b789b..7bf15754 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -223,17 +223,6 @@ TEST_P(PtcTest, PtcCommunication) std::shared_ptr status; ASSERT_NO_THROW_PRINT(status = hw_interface->GetLidarStatus()); ASSERT_NE(status, nullptr); - - switch (model) { - case SensorModel::HESAI_PANDARQT128: - case SensorModel::HESAI_PANDARXT32: - case SensorModel::HESAI_PANDARAT128: - case SensorModel::HESAI_PANDAR128_E4X: - ASSERT_NO_THROW_PRINT(hw_interface->GetPtpConfig()); - ASSERT_NO_THROW_PRINT(hw_interface->GetPtpDiagStatus()); - default: - break; - } } INSTANTIATE_TEST_SUITE_P(TestMain, PtcTest, testing::ValuesIn(g_models_under_test)); From 6fe68f5392608b84e4f6b120d0cf4c149f7e8b99 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Mon, 9 Dec 2024 13:54:54 +0900 Subject: [PATCH 31/36] add hesai_tcp struct contents check function --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 24 ++++++++++++++++++++ src/ros2_socketcan | 1 + src/transport_drivers | 1 + 3 files changed, 26 insertions(+) create mode 160000 src/ros2_socketcan create mode 160000 src/transport_drivers diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 7bf15754..23e81bd5 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #ifndef _TEST_RESOURCES_PATH static_assert(false, "No test resources path defined"); @@ -47,6 +49,7 @@ using testing::_; using testing::AtLeast; using testing::Exactly; using testing::InSequence; +using nlohmann::json; const SensorModel g_models_under_test[] = { SensorModel::HESAI_PANDAR64, SensorModel::HESAI_PANDAR40P, SensorModel::HESAI_PANDARQT64, @@ -106,6 +109,21 @@ auto make_sensor_config(SensorModel model) return std::make_shared(config); } +template +void check_hesai_struct(const std::shared_ptr& hesai_struct) +{ + const json hesai_inventory_json_data = hesai_struct->to_json(); + + const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s]+"); + std::smatch struct_match_string; + + for (const auto& [key, value] : hesai_inventory_json_data.items()) { + auto str_value = value.template get(); + bool match_result = std::regex_match(str_value, struct_match_string, struct_regex); + EXPECT_TRUE(match_result) << key << " chars are invalid. Value: " << str_value; + } +} + TEST_P(PtcTest, ConnectionLifecycle) { /* Constructor does not immediately connect, destructor closes socket */ { @@ -223,6 +241,12 @@ TEST_P(PtcTest, PtcCommunication) std::shared_ptr status; ASSERT_NO_THROW_PRINT(status = hw_interface->GetLidarStatus()); ASSERT_NE(status, nullptr); + + // check that all fields are valid + check_hesai_struct(config); + check_hesai_struct(inventory); + check_hesai_struct(status); + } INSTANTIATE_TEST_SUITE_P(TestMain, PtcTest, testing::ValuesIn(g_models_under_test)); diff --git a/src/ros2_socketcan b/src/ros2_socketcan new file mode 160000 index 00000000..4ced5284 --- /dev/null +++ b/src/ros2_socketcan @@ -0,0 +1 @@ +Subproject commit 4ced5284fdd080274b04530406d887e8330a0cdb diff --git a/src/transport_drivers b/src/transport_drivers new file mode 160000 index 00000000..86b9aae8 --- /dev/null +++ b/src/transport_drivers @@ -0,0 +1 @@ +Subproject commit 86b9aae8555dbb4a3103c21475d7f5e2d49981ab From 26680fb50ba63d3e67e3f2100a02d982b6dca7b3 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Mon, 9 Dec 2024 14:49:02 +0900 Subject: [PATCH 32/36] fix hesai_tcp struct contents check function --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 23e81bd5..57f59dcd 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -114,13 +114,15 @@ void check_hesai_struct(const std::shared_ptr& hesai_struct) { const json hesai_inventory_json_data = hesai_struct->to_json(); - const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s]+"); + const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); std::smatch struct_match_string; for (const auto& [key, value] : hesai_inventory_json_data.items()) { - auto str_value = value.template get(); - bool match_result = std::regex_match(str_value, struct_match_string, struct_regex); - EXPECT_TRUE(match_result) << key << " chars are invalid. Value: " << str_value; + if (!value.is_string()) continue; + + auto str_value = value.template get(); + bool match_result = std::regex_match(str_value, struct_match_string, struct_regex); + EXPECT_TRUE(match_result) << key << " chars are invalid. Value: " << str_value; } } From 71dea6bea2844edf64e93fdff98b924bb685f2c5 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Fri, 13 Dec 2024 15:27:39 +0900 Subject: [PATCH 33/36] fix hesai_tcp struct contents check function --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 87 +++++++++++++++++--- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 57f59dcd..aa550401 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -62,6 +62,12 @@ const uint16_t g_ptc_port = 9347; const size_t g_ptc_header_size = 8; const char g_host_ip[] = "192.168.42.42"; const char g_sensor_ip[] = "192.168.84.84"; +const int rpm_min = 0; +const int rpm_max = 1200; +const int temperature_min = -100; +const int temperature_max = 200; +const int electricity_min = 0; +const int electricity_max = 100; auto make_sensor_config(SensorModel model) { @@ -109,21 +115,78 @@ auto make_sensor_config(SensorModel model) return std::make_shared(config); } +void check_value_range(float value, std::string key) +{ + if (key == "motor_speed" || key == "spin_rate"){ + EXPECT_TRUE(0 <= value and value <= 1200); + }else if (key == "temperature"){ + EXPECT_TRUE(-100 <= value and value <= 200); + }else if (key == "input_current" || key == "input_voltage" || key == "input_power"){ + EXPECT_TRUE(0 <= value and value <= 100); + } +} + template void check_hesai_struct(const std::shared_ptr& hesai_struct) { - const json hesai_inventory_json_data = hesai_struct->to_json(); - - const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); - std::smatch struct_match_string; - - for (const auto& [key, value] : hesai_inventory_json_data.items()) { - if (!value.is_string()) continue; - - auto str_value = value.template get(); - bool match_result = std::regex_match(str_value, struct_match_string, struct_regex); - EXPECT_TRUE(match_result) << key << " chars are invalid. Value: " << str_value; - } + const json hesai_struct_json_data = hesai_struct->to_json(); + + for (const auto& [key, value] : hesai_struct_json_data.items()) { + // string + if (value.is_string()){ + std::string str_value = value.template get(); + std::smatch struct_match_string; + const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); + EXPECT_TRUE(std::regex_match(str_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: " << str_value; + + const std::regex number_regex("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)"); + std::smatch struct_match_string_float; + if (std::regex_match(str_value, struct_match_string_float, number_regex)){ + // std::cout << "true key: " << key << " value: " << str_value << std::endl; + float number_value = std::stof(str_value); + check_value_range(number_value, key); + }else{ + // std::cout << "false key: " << key << " value: " << str_value << std::endl; + continue; + } + }else if (value.is_object()) { + // Nsted JSON Object + for (const auto& [sub_key, sub_value] : value.items()) { + if (sub_value.is_string()) { + std::string str_value = sub_value.template get(); + const std::regex number_regex("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)"); + std::smatch struct_match_string_float; + + if (std::regex_search(str_value, struct_match_string_float, number_regex)) { + float number_value = std::stof(struct_match_string_float[0].str()); + check_value_range(number_value, sub_key); + } + } else if (sub_value.is_number()) { + float number_value = sub_value.is_number_float() + ? static_cast(sub_value.template get()) + : static_cast(sub_value.template get()); + check_value_range(number_value, sub_key); + } + } + }else{ + // number + std::smatch struct_match_string; + const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); + if (value.is_number()) { + if (value.is_number_float()) { + auto number_value = static_cast(value.template get()); + auto string_value = std::to_string(number_value); + EXPECT_TRUE(std::regex_match(string_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: "; + check_value_range(number_value, key); + } else if (value.is_number_integer()) { + auto number_value = static_cast(value.template get()); + auto string_value = std::to_string(number_value); + EXPECT_TRUE(std::regex_match(string_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: "; + check_value_range(number_value, key); + } + } + } + } } TEST_P(PtcTest, ConnectionLifecycle) From a7bcc1dcf59b820a29727418dfe14f6099aa9ae8 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Fri, 13 Dec 2024 15:40:04 +0900 Subject: [PATCH 34/36] fix comment --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index aa550401..43ab196a 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -150,7 +150,7 @@ void check_hesai_struct(const std::shared_ptr& hesai_struct) continue; } }else if (value.is_object()) { - // Nsted JSON Object + // Nested JSON Object for (const auto& [sub_key, sub_value] : value.items()) { if (sub_value.is_string()) { std::string str_value = sub_value.template get(); From 355e7f8b69e706b4e31fad2f83af06080f216cb1 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Fri, 13 Dec 2024 16:19:07 +0900 Subject: [PATCH 35/36] fix hesai_tcp struct contents check function --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 82 +++++++------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index 43ab196a..a82eb749 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -126,67 +126,39 @@ void check_value_range(float value, std::string key) } } -template -void check_hesai_struct(const std::shared_ptr& hesai_struct) -{ - const json hesai_struct_json_data = hesai_struct->to_json(); - - for (const auto& [key, value] : hesai_struct_json_data.items()) { - // string - if (value.is_string()){ - std::string str_value = value.template get(); - std::smatch struct_match_string; +// 値を処理する汎用関数 +void check_struct_value(const std::string& key, const json& value) { + if (value.is_string()) { + std::string str_value = value.get(); const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); - EXPECT_TRUE(std::regex_match(str_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: " << str_value; + EXPECT_TRUE(std::regex_match(str_value, struct_regex)) << key << " chars are invalid. Value: " << str_value; const std::regex number_regex("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)"); std::smatch struct_match_string_float; - if (std::regex_match(str_value, struct_match_string_float, number_regex)){ - // std::cout << "true key: " << key << " value: " << str_value << std::endl; - float number_value = std::stof(str_value); - check_value_range(number_value, key); - }else{ - // std::cout << "false key: " << key << " value: " << str_value << std::endl; - continue; - } - }else if (value.is_object()) { - // Nested JSON Object - for (const auto& [sub_key, sub_value] : value.items()) { - if (sub_value.is_string()) { - std::string str_value = sub_value.template get(); - const std::regex number_regex("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)"); - std::smatch struct_match_string_float; - - if (std::regex_search(str_value, struct_match_string_float, number_regex)) { - float number_value = std::stof(struct_match_string_float[0].str()); - check_value_range(number_value, sub_key); - } - } else if (sub_value.is_number()) { - float number_value = sub_value.is_number_float() - ? static_cast(sub_value.template get()) - : static_cast(sub_value.template get()); - check_value_range(number_value, sub_key); - } - } - }else{ - // number - std::smatch struct_match_string; - const std::regex struct_regex("[a-zA-Z0-9._%+\\-\\s:]*"); - if (value.is_number()) { - if (value.is_number_float()) { - auto number_value = static_cast(value.template get()); - auto string_value = std::to_string(number_value); - EXPECT_TRUE(std::regex_match(string_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: "; - check_value_range(number_value, key); - } else if (value.is_number_integer()) { - auto number_value = static_cast(value.template get()); - auto string_value = std::to_string(number_value); - EXPECT_TRUE(std::regex_match(string_value, struct_match_string, struct_regex)) << key << " chars are invalid. Value: "; + + if (std::regex_match(str_value, struct_match_string_float, number_regex)) { + float number_value = std::stof(struct_match_string_float[0].str()); check_value_range(number_value, key); - } } - } - } + } else if (value.is_number()) { + float number_value = value.is_number_float() ? static_cast(value.get()) : static_cast(value.get()); + check_value_range(number_value, key); + } else if (value.is_object()) { + for (const auto& [sub_key, sub_value] : value.items()) { + check_struct_value(sub_key, sub_value); + } + } else { + EXPECT_TRUE(false) << key << " contains unsupported value type."; + } +} + +// メインチェック関数 +template +void check_hesai_struct(const std::shared_ptr& hesai_struct) { + const json hesai_struct_json_data = hesai_struct->to_json(); + for (const auto& [key, value] : hesai_struct_json_data.items()) { + check_struct_value(key, value); + } } TEST_P(PtcTest, ConnectionLifecycle) From b3e328380ddfced8d9bc344c8d9a1ce0871684e9 Mon Sep 17 00:00:00 2001 From: ike-kazu Date: Fri, 13 Dec 2024 16:45:39 +0900 Subject: [PATCH 36/36] del japanese comment --- nebula_hw_interfaces/test/hesai/test_ptc.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/nebula_hw_interfaces/test/hesai/test_ptc.cpp b/nebula_hw_interfaces/test/hesai/test_ptc.cpp index a82eb749..b4fcf316 100644 --- a/nebula_hw_interfaces/test/hesai/test_ptc.cpp +++ b/nebula_hw_interfaces/test/hesai/test_ptc.cpp @@ -126,7 +126,6 @@ void check_value_range(float value, std::string key) } } -// 値を処理する汎用関数 void check_struct_value(const std::string& key, const json& value) { if (value.is_string()) { std::string str_value = value.get(); @@ -152,7 +151,6 @@ void check_struct_value(const std::string& key, const json& value) { } } -// メインチェック関数 template void check_hesai_struct(const std::shared_ptr& hesai_struct) { const json hesai_struct_json_data = hesai_struct->to_json();