diff --git a/README.md b/README.md index b52eeea..a28981f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ Add the `serial_port`, `baud_rate`, and `id` parameters to the ros2_control part 10 + 1120.0 + 170.0 + 3600.0 @@ -35,7 +38,9 @@ Add the `serial_port`, `baud_rate`, and `id` parameters to the ros2_control part ... ``` +### Optional parameters When `reboot_on_error` is "true" the motor automaticaly reboots on mechanical overload error (it still loses torque for a brief moment, but that should be enogh to let the motor recover the fault). +`p_gain`, `i_gain` and `d_gain` set the PID gain values of the motor. ## Run a test You need the urdf of the robot with the ros2_control xml added. diff --git a/include/dynamixel_ros2_control/dynamixel_driver.hpp b/include/dynamixel_ros2_control/dynamixel_driver.hpp index 46dfa61..f70f80b 100644 --- a/include/dynamixel_ros2_control/dynamixel_driver.hpp +++ b/include/dynamixel_ros2_control/dynamixel_driver.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "dynamixel_sdk/dynamixel_sdk.h" #include "iostream" @@ -20,9 +21,11 @@ class Driver { public: typedef std::function hw_error_callback; private: + class Motor; struct GroupSyncReader { GroupSyncRead syncRead; std::vector> read_functions; + std::vector motors; GroupSyncReader( std::unique_ptr& port, std::unique_ptr& ph, @@ -42,6 +45,7 @@ class Driver { bool led; bool rebooting; + bool rebooted; double goal_position; @@ -49,13 +53,16 @@ class Driver { //double goal_velocity; double velocity; - double effort; - bool torque; uint8_t hw_error; + double p_gain, i_gain, d_gain; + double p_gain_target, i_gain_target, d_gain_target; + + std::vector> after_reboot_functions; + typedef std::function hw_error_callback; //hw_error_callback error_callback; @@ -87,7 +94,17 @@ class Driver { void set_velocity_from_raw(uint32_t raw) { velocity = velocity_from_raw(raw); } void set_effort_from_raw(uint32_t raw) { effort = double(static_cast(raw))*0.1; } - Motor(const motor_id p_id, std::string p_name, const model_t type) : id(p_id), name(p_name), rebooting(false), goal_position(std::nan("")), position(std::nan("")) { + Motor(const motor_id p_id, std::string p_name, const model_t type) : + id(p_id), + name(p_name), + rebooting(true), + rebooted(false), + goal_position(std::numeric_limits::quiet_NaN()), + position(std::numeric_limits::quiet_NaN()), + p_gain(std::numeric_limits::quiet_NaN()), + i_gain(std::numeric_limits::quiet_NaN()), + d_gain(std::numeric_limits::quiet_NaN()) + { try { model = &model_infos.at(type); } catch (const std::exception &e) { @@ -111,7 +128,7 @@ class Driver { [this](uint32_t data) { this->torque = bool(data); if (this->torque == false) { - goal_position = std::nan(""); + goal_position = std::numeric_limits::quiet_NaN(); } }); @@ -176,10 +193,38 @@ class Driver { //https://emanual.robotis.com/docs/en/dxl/x/xl430-w250/#hardware-error-status } else { this->rebooting = false; + this->rebooted = true; } }); } + } + void setup_init_sync_readers( + std::map>& initSyncReaders, + std::unique_ptr& portHandler, + std::unique_ptr& packetHandler) + { + //Load PID gains if available + try { + setup_sync_reader( + initSyncReaders, portHandler, packetHandler, + command::Position_P_Gain, + [this](uint32_t raw) { + this->p_gain = double(static_cast(raw)); + }); + setup_sync_reader( + initSyncReaders, portHandler, packetHandler, + command::Position_I_Gain, + [this](uint32_t raw) { + this->i_gain = double(static_cast(raw)); + }); + setup_sync_reader( + initSyncReaders, portHandler, packetHandler, + command::Position_D_Gain, + [this](uint32_t raw) { + this->d_gain = double(static_cast(raw)); + }); + } catch (command_type_not_implemented& e) {} } void setup_sync_reader( @@ -199,6 +244,7 @@ class Driver { std::make_unique(portHandler, packetHandler, sr_field)}); } sync_reader_it->second->syncRead.addParam(this->id); + sync_reader_it->second->motors.push_back(this); sync_reader_it->second->read_functions.push_back( [this, sr_field, callback](GroupSyncRead& read) { if (read.isAvailable(this->id, sr_field.address, sr_field.data_length)) { @@ -216,6 +262,7 @@ class Driver { std::unique_ptr portHandler; std::unique_ptr packetHandler; + std::map> initSyncReaders; std::map> syncReaders; std::map> syncWrites; @@ -285,7 +332,11 @@ class Driver { add_syncwrite_data(motor->id, command_field, reinterpret_cast(&p)); } - void reboot(const dynamixel::Driver::Motor::Ptr& motor) { + void reboot(dynamixel::Driver::Motor::Ptr& motor) { + motor->after_reboot_functions.clear(); + motor->after_reboot_functions.push_back([this, motor](){ + this->pid_gains(motor->name, motor->p_gain_target, motor->i_gain_target, motor->d_gain_target); + }); packetHandler->reboot(portHandler.get(),motor->id); } @@ -367,6 +418,8 @@ class Driver { auto motor = std::make_shared(id, joint_name, type); motors[joint_name] = motor; std::weak_ptr weak_motor(motors[joint_name]); + + motor->setup_init_sync_readers(initSyncReaders, portHandler, packetHandler); motor->setup_sync_readers(syncReaders, portHandler, packetHandler, [this, weak_motor](dynamixel::hardware_status s) { @@ -415,6 +468,22 @@ class Driver { } void read() { + bool init_done = true; + for (auto it = initSyncReaders.begin(); it != initSyncReaders.end(); std::advance(it,1)) { + const auto& [field, syncReader] = *it; + (void)field; + /*auto result = */ syncReader->syncRead.txRxPacket(); + //std::cout<getTxRxResult(result)<read_functions) { + read_f(syncReader->syncRead); + } + // If no motor is rebooting then the initial read was succesfull. + if (!std::all_of(syncReader->motors.cbegin(), syncReader->motors.cend(), + [](const auto& m){ return !m->rebooting; })) { + init_done = false; + } + } + for (const auto& [field, syncReader] : syncReaders) { (void)field; /*auto result = */ syncReader->syncRead.txRxPacket(); @@ -423,6 +492,18 @@ class Driver { read_f(syncReader->syncRead); } } + + if(init_done) { + initSyncReaders.clear(); + for (const auto& [joint_name, motor]: motors) { + if(motor->rebooted) { + motor->rebooted = false; + for(auto after_reboot_f:motor->after_reboot_functions) { + after_reboot_f(); + } + } + } + } } /** @@ -579,7 +660,6 @@ class Driver { * @param joint_name the name of the joint * @param position the position in radians the actuator should move to * @param enable_torque enable the torque if not enabled already - * @return true if successfull, false otherwise (e.g. joint_name is not available) */ void set_position(const std::string& joint_name, double position, bool enable_torque = false) { if (std::isnan(position)) @@ -603,6 +683,54 @@ class Driver { add_syncwrite_data(motor->id, command_field, reinterpret_cast(&p)); } + /** + * Sets the pid gains for a motor. This is only possible after the first read + * as the initial values need to be read from the motor. + * @param joint_name the name of the joint + */ + + void pid_gains(const std::string& joint_name, double& p, double& i, double& d) { + auto it_motor = motors.find(joint_name); + if (it_motor == motors.end()) + throw std::invalid_argument("Joint \"" + joint_name + "\" not added to driver!"); + auto motor = it_motor->second; + //std::cout <p_gain<<"\ti:"<< motor->i_gain<<"\td:" << motor->d_gain << std::endl; + if(std::isnan( motor->p_gain) || std::isnan( motor->i_gain) || std::isnan( motor->d_gain)) { + throw std::invalid_argument("Cant set pid gains for \"" + joint_name + "\" because motor doesn't have pid control!"); + } + if(std::isnan(p)) { + p = motor->p_gain; + } + if(std::isnan(i)) { + i = motor->i_gain; + } + if(std::isnan(d)) { + d = motor->d_gain; + } + if( p != motor->p_gain) { + field command_field; + motor->get_command(command::Position_P_Gain, command_field); + uint32_t data = static_cast(p); + motor->p_gain_target = p; + add_syncwrite_data(motor->id, command_field, reinterpret_cast(&data)); + } + if( i != motor->i_gain) { + field command_field; + motor->get_command(command::Position_I_Gain, command_field); + uint32_t data = static_cast(i); + motor->i_gain_target = i; + add_syncwrite_data(motor->id, command_field, reinterpret_cast(&data)); + } + if( d != motor->d_gain) { + field command_field; + motor->get_command(command::Position_D_Gain, command_field); + uint32_t data = static_cast(d); + motor->d_gain_target = d; + add_syncwrite_data(motor->id, command_field, reinterpret_cast(&data)); + } + } + /** * reboots a joint. * @param joint_name the name of the joint diff --git a/src/dynamixel_ros2_control.cpp b/src/dynamixel_ros2_control.cpp index eacfe1c..874821a 100644 --- a/src/dynamixel_ros2_control.cpp +++ b/src/dynamixel_ros2_control.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "dynamixel_ros2_control/dynamixel_driver.hpp" @@ -38,6 +39,9 @@ #define DYN_PORTNAME_PARAM_STR "serial_port" #define DYN_ENABLE_TORQUE_ON_START_PARAM_STR "enable_torque_on_startup" #define DYN_AUTO_REBOOT_PARAM_STR "reboot_on_error" +#define DYN_P_GAIN_PARAM_STR "p_gain" +#define DYN_I_GAIN_PARAM_STR "i_gain" +#define DYN_D_GAIN_PARAM_STR "d_gain" #define DYN_ID_PARAM_STR "id" #define DYN_DEFAULT_PORT_STR "/dev/ttyUSB0" @@ -111,9 +115,43 @@ CallbackReturn DynamixelHardwareInterface::on_init( (std::string("Motors count: ") + std::to_string(dynamixel_driver_->get_motor_count())) .c_str()); + + dynamixel_driver_->read(); + + for (const auto &joint : info_.joints) { + auto p_it = joint.parameters.find(DYN_P_GAIN_PARAM_STR); + double p = (p_it != joint.parameters.end()) ? std::stod(p_it->second) + : std::numeric_limits::quiet_NaN(); + auto i_it = joint.parameters.find(DYN_I_GAIN_PARAM_STR); + double i = (i_it != joint.parameters.end()) ? std::stod(i_it->second) + : std::numeric_limits::quiet_NaN(); + auto d_it = joint.parameters.find(DYN_D_GAIN_PARAM_STR); + double d = (d_it != joint.parameters.end()) ? std::stod(d_it->second) + : std::numeric_limits::quiet_NaN(); + try { + dynamixel_driver_->pid_gains(joint.name, p, i, d); + if (!std::isnan(p) || !std::isnan(i) || !std::isnan(d)) { + RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + ("Joint-Actuator \"" + std::string(joint.name) + + "\" p:%f\t i:%f\t d:%f") + .c_str(), + float(p), float(i), float(d)); + } + } catch (const std::exception &e) { + if (p_it != joint.parameters.end() || i_it != joint.parameters.end() || + d_it != joint.parameters.end()) { + RCLCPP_WARN(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + ("Joint-Actuator \"" + std::string(joint.name) + + "\" Cant set PID gains: " + e.what()) + .c_str()); + } + } + } if (failure) { return CallbackReturn::ERROR; } + RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + "init success"); return CallbackReturn::SUCCESS; } @@ -158,7 +196,7 @@ void DynamixelHardwareInterface::overload_error_callback( std::vector DynamixelHardwareInterface::export_state_interfaces() { - RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), "export_state_interfaces"); std::vector state_interfaces; dynamixel_driver_->for_each_joint([&](const std::string &joint_name) { @@ -177,7 +215,7 @@ DynamixelHardwareInterface::export_state_interfaces() { std::vector DynamixelHardwareInterface::export_command_interfaces() { - RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), "export_command_interfaces"); std::vector command_interfaces; dynamixel_driver_->for_each_joint([&](const std::string &joint_name) { @@ -193,7 +231,7 @@ CallbackReturn DynamixelHardwareInterface::on_activate( RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), "on_activate"); try { dynamixel_driver_->ping_all(); - RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), "Pinging dynamixels was successfull"); } catch (const dynamixel::Driver::dynamixel_bus_error &e) { RCLCPP_ERROR(rclcpp::get_logger(DYN_LOGGER_NAME_STR), e.what()); @@ -211,6 +249,8 @@ CallbackReturn DynamixelHardwareInterface::on_activate( RCLCPP_ERROR(rclcpp::get_logger(DYN_LOGGER_NAME_STR), e.what()); return CallbackReturn::ERROR; } + RCLCPP_DEBUG(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + "activate success"); return CallbackReturn::SUCCESS; } @@ -221,7 +261,8 @@ CallbackReturn DynamixelHardwareInterface::on_deactivate( dynamixel_driver_->read(); dynamixel_driver_->set_torque_all(false); dynamixel_driver_->write(); - RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), "Motors deaktivated. Torque disabled !"); + RCLCPP_INFO(rclcpp::get_logger(DYN_LOGGER_NAME_STR), + "Motors deaktivated. Torque disabled !"); } catch (std::invalid_argument &e) { RCLCPP_WARN(rclcpp::get_logger(DYN_LOGGER_NAME_STR), e.what()); return CallbackReturn::ERROR;