#include "cyphal_hardware_interface/simplesync_system.hpp" #include #include #include #include #include #include #include #include #include #include #include "serial.hpp" #include "hardware_interface/types/hardware_interface_type_values.hpp" #include "rclcpp/rclcpp.hpp" #include "simplesync.hpp" namespace cyphal_hardware_interface { cyphalSystemHardware::~cyphalSystemHardware() { if (serial_port_handle) { RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Closing port '%s'", serial_port_.c_str()); close(serial_port_handle); } } hardware_interface::CallbackReturn cyphalSystemHardware::on_init( const hardware_interface::HardwareInfo &info) { if (hardware_interface::SystemInterface::on_init(info) != hardware_interface::CallbackReturn::SUCCESS) { return hardware_interface::CallbackReturn::ERROR; } serial_port_ = default_serial_port_; if (info_.hardware_parameters.count("port") > 0) { serial_port_ = info_.hardware_parameters.at("port"); } else { RCLCPP_WARN(rclcpp::get_logger("cyphalSystemHardware"), "No port specified in urdf, using default value"); } if (info_.hardware_parameters.count("try_next_port_number") > 0) { std::string try_next_str = info_.hardware_parameters.at("try_next_port_number"); try_next_port_number = std::set({"True", "true", "1", "Yes", "yes", "Y", "y"}) .count(try_next_str) > 0; RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Trying next ports if the one provided is not openable"); } RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Port set as %s", serial_port_.c_str()); std::vector components; components.insert(components.begin(), info_.joints.begin(), info_.joints.end()); components.insert(components.begin(), info_.sensors.begin(), info_.sensors.end()); components.insert(components.begin(), info_.gpios.begin(), info_.gpios.end()); for (const hardware_interface::ComponentInfo &joint : components) { // StaubiSystem has exactly two states and one command interface on each // joint /*if (joint.command_interfaces.size() != 1) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "Joint '%s' has %zu command interfaces found. 1 expected.", joint.name.c_str(), joint.command_interfaces.size()); return hardware_interface::CallbackReturn::ERROR; }*/ for (const auto &i : joint.command_interfaces) { std::string param_name = "command:" + i.name; if (joint.parameters.count(param_name) <= 0) { RCLCPP_FATAL( rclcpp::get_logger("cyphalSystemHardware"), "command interface '%s' of joint/gpio '%s' has no parameter " "'command:%s'.", i.name.c_str(), joint.name.c_str(), i.name.c_str()); return hardware_interface::CallbackReturn::ERROR; } auto interface_name = joint.parameters.at(param_name); if (interfaces_map.count(interface_name) > 0) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "Sync Interface '%s' used twice. FATAL ERROR.", interface_name.c_str()); return hardware_interface::CallbackReturn::ERROR; } interfaces_map[interface_name] = { std::numeric_limits::quiet_NaN(), (joint.parameters.count("factor") > 0) ? std::stod(joint.parameters.at("factor")) : 1.0, joint.name, true, i.name}; } for (const auto &i : joint.state_interfaces) { std::string param_name = "state:" + i.name; if (joint.parameters.count(param_name) <= 0) { RCLCPP_FATAL( rclcpp::get_logger("cyphalSystemHardware"), "state interface '%s' of joint/sensor/gpio '%s' has no parameter " "'state:%s'.", i.name.c_str(), joint.name.c_str(), i.name.c_str()); return hardware_interface::CallbackReturn::ERROR; } auto interface_name = joint.parameters.at(param_name); if (interfaces_map.count(interface_name) > 0) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "Sync Interface '%s' used twice. FATAL ERROR.", interface_name.c_str()); return hardware_interface::CallbackReturn::ERROR; } interfaces_map[interface_name] = { std::numeric_limits::quiet_NaN(), (joint.parameters.count("factor") > 0) ? std::stod(joint.parameters.at("factor")) : 1.0, joint.name, false, i.name}; } } // for (const auto &[key, value] : interfaces_map) { // RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), // "Interface %s added (factor: %f).", key.c_str(), value.factor); // } return hardware_interface::CallbackReturn::SUCCESS; } // namespace cyphal_hardware_interface std::vector cyphalSystemHardware::export_state_interfaces() { std::vector state_interfaces; for (auto &[key, value] : interfaces_map) { if (!value.command) state_interfaces.emplace_back(hardware_interface::StateInterface( value.joint, value.control_interface, &value.value)); } /* for (auto i = 0u; i < info_.joints.size(); i++) { state_interfaces.emplace_back(hardware_interface::StateInterface( info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_positions_[i])); state_interfaces.emplace_back(hardware_interface::StateInterface( info_.joints[i].name, hardware_interface::HW_IF_VELOCITY, &hw_velocities_[i])); } */ return state_interfaces; } std::vector cyphalSystemHardware::export_command_interfaces() { std::vector command_interfaces; for (auto &[key, value] : interfaces_map) { if (value.command) command_interfaces.emplace_back(hardware_interface::CommandInterface( value.joint, value.control_interface, &value.value)); } /* for (auto i = 0u; i < info_.joints.size(); i++) { command_interfaces.emplace_back(hardware_interface::CommandInterface( info_.joints[i].name, hardware_interface::HW_IF_VELOCITY, &hw_commands_[i])); } */ return command_interfaces; } hardware_interface::CallbackReturn cyphalSystemHardware::on_configure( const rclcpp_lifecycle::State & /*previous_state*/) { // const hardware_interface::CallbackReturn result = // can_interface_->connect(can_socket_) // ? hardware_interface::CallbackReturn::SUCCESS // : hardware_interface::CallbackReturn::FAILURE; // Open the serial port. Change device path as needed (currently set to an // standard FTDI USB-UART cable type device) serial_port_handle = open_serial(serial_port_); if (!serial_port_handle) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "Error opening port '%s': %s", serial_port_.c_str(), strerror(errno)); if (try_next_port_number) { for (unsigned int tries = 0; tries < 100; tries += 1) { size_t digits_idx = serial_port_.size() - 1; while (digits_idx > 0 && std::isdigit(serial_port_[digits_idx])) digits_idx -= 1; digits_idx += 1; std::string number_str = serial_port_.substr(digits_idx); unsigned int number = number_str.size() > 0 ? std::stol(number_str) : 0; serial_port_ = serial_port_.substr(0, digits_idx) + std::to_string(number + 1); serial_port_handle = open_serial(serial_port_); if (!serial_port_handle) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "Error opening port '%s': %s", serial_port_.c_str(), strerror(errno)); } else break; } } if (!serial_port_handle) return hardware_interface::CallbackReturn::FAILURE; } RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Successfuly opened port '%s'", serial_port_.c_str()); sync = std::make_unique( [this](uint8_t *buf, unsigned int buf_size) { std::stringstream ss; ss << std::hex; for (unsigned int i = 0; i < buf_size; i += 1) ss << (int)buf[i] << " "; // RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), // "Write to serial: %s", ss.str().c_str()); write_serial(this->serial_port_handle, buf, buf_size); return 0; }, []() { return (std::chrono::steady_clock::now()); }); sync->request_all_interfaces(); using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); // FIXME: callback uint8_t read_buf[1024]; int read_buf_used = read_serial(serial_port_handle, read_buf, sizeof(read_buf)); sync->handle_stream((uint8_t *)read_buf, read_buf_used); for (auto &i : sync->interfaces) { if (!i) continue; RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Sync-interface %s recived.", i->key.c_str()); } return hardware_interface::CallbackReturn::SUCCESS; } hardware_interface::CallbackReturn cyphalSystemHardware::on_activate( const rclcpp_lifecycle::State & /*previous_state*/) { // BEGIN: This part here is for exemplary purposes - Please do not copy to // your production code RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Activating ...please wait..."); /* for (auto i = 0; i < hw_start_sec_; i++) { rclcpp::sleep_for(std::chrono::seconds(1)); RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "%.1f seconds left...", hw_start_sec_ - i); } // END: This part here is for exemplary purposes - Please do not copy to your // production code // set some default values for (auto i = 0u; i < hw_positions_.size(); i++) { if (std::isnan(hw_positions_[i])) { hw_positions_[i] = 0; hw_velocities_[i] = 0; hw_commands_[i] = 0; } } */ RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Successfully activated!"); return hardware_interface::CallbackReturn::SUCCESS; } hardware_interface::CallbackReturn cyphalSystemHardware::on_deactivate( const rclcpp_lifecycle::State & /*previous_state*/) { // BEGIN: This part here is for exemplary purposes - Please do not copy to // your production code RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Deactivating ...please wait..."); /* for (auto i = 0; i < hw_stop_sec_; i++) { rclcpp::sleep_for(std::chrono::seconds(1)); RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "%.1f seconds left...", hw_stop_sec_ - i); } // END: This part here is for exemplary purposes - Please do not copy to your // production code RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Successfully deactivated!"); */ return hardware_interface::CallbackReturn::SUCCESS; } hardware_interface::return_type cyphalSystemHardware::read(const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) { // BEGIN: This part here is for exemplary purposes - Please do not copy to // your production code /* for (std::size_t i = 0; i < hw_velocities_.size(); i++) { // Simulate Staubi wheels's movement as a first-order system // Update the joint status: this is a revolute joint without any limit. // Simply integrates hw_positions_[i] = hw_positions_[i] + period.seconds() * hw_velocities_[i]; RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Got position state %.5f and velocity state %.5f for '%s'!", hw_positions_[i], hw_velocities_[i], info_.joints[i].name.c_str()); } // END: This part here is for exemplary purposes - Please do not copy to your // production code */ // Allocate memory for read buffer, set size according to your needs // Read bytes. The behaviour of read() (e.g. does it block?, // how long does it block for?) depends on the configuration // settings above, specifically VMIN and VTIME // char read_buf[256]; // int num_bytes = read(serial_port, &read_buf, sizeof(read_buf)); // // n is the number of bytes read. n may be 0 if no bytes were received, // and // // can also be -1 to signal an error. // if (num_bytes < 0) { // printf("Error reading: %s", strerror(errno)); // return 1; // } // // Here we assume we received ASCII data, but you might be sending raw // bytes // // (in that case, don't try and print it to the screen like this!) // printf("Read %i bytes. Received message: %s", num_bytes, read_buf); // close(serial_port); if (!sync) return hardware_interface::return_type::ERROR; uint8_t read_buf[1024]; int read_buf_used = read_serial(serial_port_handle, read_buf, sizeof(read_buf)); sync->handle_stream((uint8_t *)read_buf, read_buf_used); for (auto &[key, value] : interfaces_map) { // RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), // "Interface %s added (factor: %f).", key.c_str(), // value.factor); if (!value.command) { auto n = sync->get_number(key.c_str()); if (n) value.value = value.factor * (*n); } } using namespace std::chrono_literals; if (std::chrono::steady_clock::now() - sync->last_pkg_recived > std::chrono::milliseconds(1000)) { RCLCPP_FATAL(rclcpp::get_logger("cyphalSystemHardware"), "last package recivew more than 1000ms ago. aborting!"); return hardware_interface::return_type::ERROR; } static int counter = 0; if (counter++ % 20 == 0) for (auto &i : sync->interfaces) { if (!i) continue; auto number_ptr = sync->get_number(i->key.c_str()); auto str_ptr = sync->get_string(i->key.c_str()); if (number_ptr) RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Sync: %s = %d", i->key.c_str(), *number_ptr); if (str_ptr) RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Sync: %s = %s", i->key.c_str(), str_ptr->c_str()); } return hardware_interface::return_type::OK; } hardware_interface::return_type cyphal_hardware_interface ::cyphalSystemHardware::write( const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) { // BEGIN: This part here is for exemplary purposes - Please do not copy to // your production code if (sync) { for (auto &[key, value] : interfaces_map) { // RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), // "Interface %s added (factor: %f).", key.c_str(), // value.factor); if (value.command) { auto ni = sync->get_or_create_interface(0x01, key.c_str()); if (ni) { *(int *)ni->data = (value.value / value.factor); ni->send_requested = true; // RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), // "update: %s set to %d ", key.c_str(), *(int // *)ni->data); } } } sync->update(); } /* for (auto i = 0u; i < hw_commands_.size(); i++) { // Simulate sending commands to the hardware RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Got command %.5f for '%s'!", hw_commands_[i], info_.joints[i].name.c_str()); hw_velocities_[i] = hw_commands_[i]; } RCLCPP_INFO(rclcpp::get_logger("cyphalSystemHardware"), "Joints successfully written!"); // END: This part here is for exemplary purposes - Please do not copy to your // production code */ return hardware_interface::return_type::OK; } } // namespace cyphal_hardware_interface #include "pluginlib/class_list_macros.hpp" PLUGINLIB_EXPORT_CLASS(cyphal_hardware_interface::cyphalSystemHardware, hardware_interface::SystemInterface)