// Copyright 2025 Enactic, 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. #include "openarm_hardware/v10_simple_hardware.hpp" #include #include #include #include #include #include "hardware_interface/types/hardware_interface_type_values.hpp" #include "rclcpp/logging.hpp" #include "rclcpp/rclcpp.hpp" namespace openarm_hardware { OpenArm_v10HW::OpenArm_v10HW() = default; bool OpenArm_v10HW::parse_config(const hardware_interface::HardwareInfo& info) { // Parse CAN interface (default: can0) auto it = info.hardware_parameters.find("can_interface"); can_interface_ = (it != info.hardware_parameters.end()) ? it->second : "can0"; // Parse arm prefix (default: empty for single arm, "left_" or "right_" for // bimanual) it = info.hardware_parameters.find("arm_prefix"); arm_prefix_ = (it != info.hardware_parameters.end()) ? it->second : ""; // Parse gripper enable (default: true for V10) it = info.hardware_parameters.find("hand"); if (it == info.hardware_parameters.end()) { hand_ = true; // Default to true for V10 } else { // Handle both "true"/"True" and "false"/"False" std::string value = it->second; std::transform(value.begin(), value.end(), value.begin(), ::tolower); hand_ = (value == "true"); } // Parse CAN-FD enable (default: true for V10) it = info.hardware_parameters.find("can_fd"); if (it == info.hardware_parameters.end()) { can_fd_ = true; // Default to true for V10 } else { // Handle both "true"/"True" and "false"/"False" std::string value = it->second; std::transform(value.begin(), value.end(), value.begin(), ::tolower); can_fd_ = (value == "true"); } RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Configuration: CAN=%s, arm_prefix=%s, hand=%s, can_fd=%s", can_interface_.c_str(), arm_prefix_.c_str(), hand_ ? "enabled" : "disabled", can_fd_ ? "enabled" : "disabled"); return true; } void OpenArm_v10HW::generate_joint_names() { joint_names_.clear(); // TODO: read from urdf properly and sort in the future. // Currently, the joint names are hardcoded for order consistency to align // with hardware. Generate arm joint names: openarm_{arm_prefix}joint{N} for (size_t i = 1; i <= ARM_DOF; ++i) { std::string joint_name = "openarm_" + arm_prefix_ + "joint" + std::to_string(i); joint_names_.push_back(joint_name); } // Generate gripper joint name if enabled if (hand_) { std::string gripper_joint_name = "openarm_" + arm_prefix_ + "finger_joint1"; joint_names_.push_back(gripper_joint_name); RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Added gripper joint: %s", gripper_joint_name.c_str()); } else { RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Gripper joint NOT added because hand_=false"); } RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Generated %zu joint names for arm prefix '%s'", joint_names_.size(), arm_prefix_.c_str()); } hardware_interface::CallbackReturn OpenArm_v10HW::on_init( const hardware_interface::HardwareInfo& info) { if (hardware_interface::SystemInterface::on_init(info) != CallbackReturn::SUCCESS) { return CallbackReturn::ERROR; } // Parse configuration if (!parse_config(info)) { return CallbackReturn::ERROR; } // Generate joint names based on arm prefix generate_joint_names(); // Validate joint count (7 arm joints + optional gripper) size_t expected_joints = ARM_DOF + (hand_ ? 1 : 0); if (joint_names_.size() != expected_joints) { RCLCPP_ERROR(rclcpp::get_logger("OpenArm_v10HW"), "Generated %zu joint names, expected %zu", joint_names_.size(), expected_joints); return CallbackReturn::ERROR; } // Initialize OpenArm with configurable CAN-FD setting RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Initializing OpenArm on %s with CAN-FD %s...", can_interface_.c_str(), can_fd_ ? "enabled" : "disabled"); openarm_ = std::make_unique(can_interface_, can_fd_); // Initialize arm motors with V10 defaults openarm_->init_arm_motors(DEFAULT_MOTOR_TYPES, DEFAULT_SEND_CAN_IDS, DEFAULT_RECV_CAN_IDS); // Initialize gripper if enabled if (hand_) { RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Initializing gripper..."); openarm_->init_gripper_motor(DEFAULT_GRIPPER_MOTOR_TYPE, DEFAULT_GRIPPER_SEND_CAN_ID, DEFAULT_GRIPPER_RECV_CAN_ID); } // Initialize state and command vectors based on generated joint count const size_t total_joints = joint_names_.size(); pos_commands_.resize(total_joints, 0.0); vel_commands_.resize(total_joints, 0.0); tau_commands_.resize(total_joints, 0.0); pos_states_.resize(total_joints, 0.0); vel_states_.resize(total_joints, 0.0); tau_states_.resize(total_joints, 0.0); RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "OpenArm V10 Simple HW initialized successfully"); return CallbackReturn::SUCCESS; } hardware_interface::CallbackReturn OpenArm_v10HW::on_configure( const rclcpp_lifecycle::State& /*previous_state*/) { // Set callback mode to ignore during configuration openarm_->refresh_all(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); openarm_->recv_all(); return CallbackReturn::SUCCESS; } std::vector OpenArm_v10HW::export_state_interfaces() { std::vector state_interfaces; for (size_t i = 0; i < joint_names_.size(); ++i) { state_interfaces.emplace_back(hardware_interface::StateInterface( joint_names_[i], hardware_interface::HW_IF_POSITION, &pos_states_[i])); state_interfaces.emplace_back(hardware_interface::StateInterface( joint_names_[i], hardware_interface::HW_IF_VELOCITY, &vel_states_[i])); state_interfaces.emplace_back(hardware_interface::StateInterface( joint_names_[i], hardware_interface::HW_IF_EFFORT, &tau_states_[i])); } return state_interfaces; } std::vector OpenArm_v10HW::export_command_interfaces() { std::vector command_interfaces; // TODO: consider exposing only needed interfaces to avoid undefined behavior. for (size_t i = 0; i < joint_names_.size(); ++i) { command_interfaces.emplace_back(hardware_interface::CommandInterface( joint_names_[i], hardware_interface::HW_IF_POSITION, &pos_commands_[i])); command_interfaces.emplace_back(hardware_interface::CommandInterface( joint_names_[i], hardware_interface::HW_IF_VELOCITY, &vel_commands_[i])); command_interfaces.emplace_back(hardware_interface::CommandInterface( joint_names_[i], hardware_interface::HW_IF_EFFORT, &tau_commands_[i])); } return command_interfaces; } hardware_interface::CallbackReturn OpenArm_v10HW::on_activate( const rclcpp_lifecycle::State& /*previous_state*/) { RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Activating OpenArm V10..."); openarm_->set_callback_mode_all(openarm::damiao_motor::CallbackMode::STATE); openarm_->enable_all(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); openarm_->recv_all(); // Return to zero position return_to_zero(); RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "OpenArm V10 activated"); return CallbackReturn::SUCCESS; } hardware_interface::CallbackReturn OpenArm_v10HW::on_deactivate( const rclcpp_lifecycle::State& /*previous_state*/) { RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Deactivating OpenArm V10..."); // Disable all motors (like full_arm.cpp exit) openarm_->disable_all(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); openarm_->recv_all(); RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "OpenArm V10 deactivated"); return CallbackReturn::SUCCESS; } hardware_interface::return_type OpenArm_v10HW::read( const rclcpp::Time& /*time*/, const rclcpp::Duration& /*period*/) { // Receive all motor states openarm_->refresh_all(); openarm_->recv_all(); // Read arm joint states const auto& arm_motors = openarm_->get_arm().get_motors(); for (size_t i = 0; i < ARM_DOF && i < arm_motors.size(); ++i) { pos_states_[i] = arm_motors[i].get_position(); vel_states_[i] = arm_motors[i].get_velocity(); tau_states_[i] = arm_motors[i].get_torque(); } // Read gripper state if enabled if (hand_ && joint_names_.size() > ARM_DOF) { const auto& gripper_motors = openarm_->get_gripper().get_motors(); if (!gripper_motors.empty()) { // TODO the mappings are approximates // Convert motor position (radians) to joint value (0-0.044m) double motor_pos = gripper_motors[0].get_position(); pos_states_[ARM_DOF] = motor_radians_to_joint(motor_pos); // Unimplemented: Velocity and torque mapping vel_states_[ARM_DOF] = 0; // gripper_motors[0].get_velocity(); tau_states_[ARM_DOF] = 0; // gripper_motors[0].get_torque(); } } return hardware_interface::return_type::OK; } hardware_interface::return_type OpenArm_v10HW::write( const rclcpp::Time& /*time*/, const rclcpp::Duration& /*period*/) { // Control arm motors with MIT control std::vector arm_params; for (size_t i = 0; i < ARM_DOF; ++i) { arm_params.push_back({DEFAULT_KP[i], DEFAULT_KD[i], pos_commands_[i], vel_commands_[i], tau_commands_[i]}); } openarm_->get_arm().mit_control_all(arm_params); // Control gripper if enabled if (hand_ && joint_names_.size() > ARM_DOF) { // TODO the true mappings are unimplemented. double motor_command = joint_to_motor_radians(pos_commands_[ARM_DOF]); openarm_->get_gripper().mit_control_all( {{GRIPPER_DEFAULT_KP, GRIPPER_DEFAULT_KD, motor_command, 0, 0}}); } openarm_->recv_all(1000); return hardware_interface::return_type::OK; } void OpenArm_v10HW::return_to_zero() { RCLCPP_INFO(rclcpp::get_logger("OpenArm_v10HW"), "Returning to zero position..."); // Return arm to zero with MIT control std::vector arm_params; for (size_t i = 0; i < ARM_DOF; ++i) { arm_params.push_back({DEFAULT_KP[i], DEFAULT_KD[i], 0.0, 0.0, 0.0}); } openarm_->get_arm().mit_control_all(arm_params); // Return gripper to zero if enabled if (hand_) { openarm_->get_gripper().mit_control_all( {{GRIPPER_DEFAULT_KP, GRIPPER_DEFAULT_KD, GRIPPER_JOINT_0_POSITION, 0.0, 0.0}}); } std::this_thread::sleep_for(std::chrono::microseconds(1000)); openarm_->recv_all(); } // Gripper mapping helper functions double OpenArm_v10HW::joint_to_motor_radians(double joint_value) { // Joint 0=closed -> motor 0 rad, Joint 0.044=open -> motor -1.0472 rad return (joint_value / GRIPPER_JOINT_0_POSITION) * GRIPPER_MOTOR_1_RADIANS; // Scale from 0-0.044 to 0 to -1.0472 } double OpenArm_v10HW::motor_radians_to_joint(double motor_radians) { // Motor 0 rad=closed -> joint 0, Motor -1.0472 rad=open -> joint 0.044 return GRIPPER_JOINT_0_POSITION * (motor_radians / GRIPPER_MOTOR_1_RADIANS); // Scale from 0 to -1.0472 to 0-0.044 } } // namespace openarm_hardware #include "pluginlib/class_list_macros.hpp" PLUGINLIB_EXPORT_CLASS(openarm_hardware::OpenArm_v10HW, hardware_interface::SystemInterface)