diff --git a/.gitignore b/.gitignore
index 86668ec0..2bf691f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ build/
install/
log/
.vscode/
+.codex
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/src/description/ros2_control/arm/arm.base_limit_switch.ros2_control.xacro b/src/description/ros2_control/arm/arm.base_limit_switch.ros2_control.xacro
new file mode 100644
index 00000000..29735835
--- /dev/null
+++ b/src/description/ros2_control/arm/arm.base_limit_switch.ros2_control.xacro
@@ -0,0 +1,27 @@
+
+
+
+
+
+ limit_switch_ros2_control/LimitSwitchHardwareInterface
+ can1
+ 0x130
+ 10
+ 5
+ 0
+
+
+
+ 0
+
+
+
+
+
+ 1
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.end_effector_limit_switch.ros2_control.xacro b/src/description/ros2_control/arm/arm.end_effector_limit_switch.ros2_control.xacro
new file mode 100644
index 00000000..597e9576
--- /dev/null
+++ b/src/description/ros2_control/arm/arm.end_effector_limit_switch.ros2_control.xacro
@@ -0,0 +1,33 @@
+
+
+
+
+
+ limit_switch_ros2_control/LimitSwitchHardwareInterface
+ can1
+ 0x60
+ 10
+ 5
+ 0
+
+
+
+ 2
+
+
+
+
+
+ 3
+
+
+
+
+
+ 4
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.rmd.ros2_control.xacro b/src/description/ros2_control/arm/arm.rmd.ros2_control.xacro
index 37d243cf..15c0a997 100644
--- a/src/description/ros2_control/arm/arm.rmd.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.rmd.ros2_control.xacro
@@ -7,7 +7,7 @@
10
5
0
- can0
+ can1
@@ -15,10 +15,18 @@
100
1.0
150
+
+
+
+
+
+
+
+
@@ -26,10 +34,18 @@
100
-1.0
150
+
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.rotary_encoder.ros2_control.xacro b/src/description/ros2_control/arm/arm.rotary_encoder.ros2_control.xacro
new file mode 100644
index 00000000..e24ece2c
--- /dev/null
+++ b/src/description/ros2_control/arm/arm.rotary_encoder.ros2_control.xacro
@@ -0,0 +1,39 @@
+
+
+
+
+
+ rotary_encoder_ros2_control/RotaryEncoderHardwareInterface
+ can1
+ 0x130
+ 10
+ 5
+ 0
+
+
+
+ 5
+ 0
+
+
+
+
+
+
+ 5
+ 1
+
+
+
+
+
+
+ 5
+ 2
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.servo.ros2_control.xacro b/src/description/ros2_control/arm/arm.servo.ros2_control.xacro
index 434514fc..16885fab 100644
--- a/src/description/ros2_control/arm/arm.servo.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.servo.ros2_control.xacro
@@ -7,7 +7,7 @@
1
5
0
- can0
+ can1
0x80
@@ -20,7 +20,50 @@
prismatic
-
+
+
+
+
+
+
+
+
+
+
+
+ 0x2
+ 1
+ 180
+ standard
+ revolute
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0x3
+ 1
+ 180
+ standard
+ revolute
+
+
+
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.smc_2dof.ros2_control.xacro b/src/description/ros2_control/arm/arm.smc_2dof.ros2_control.xacro
index 1988b0f4..05a1446d 100644
--- a/src/description/ros2_control/arm/arm.smc_2dof.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.smc_2dof.ros2_control.xacro
@@ -7,7 +7,7 @@
10
5
0
- can0
+ can1
@@ -17,36 +17,54 @@
300
+
+
+
+
+
+
- 0x145
+ 0x147
40
1.0
1000
+
+
+
+
+
+
- 0x147
+ 0x145
40
1.0
1000
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.smc_3dof.ros2_control.xacro b/src/description/ros2_control/arm/arm.smc_3dof.ros2_control.xacro
index e50c884a..a79076ce 100644
--- a/src/description/ros2_control/arm/arm.smc_3dof.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.smc_3dof.ros2_control.xacro
@@ -7,7 +7,7 @@
10
5
0
- can0
+ can1
@@ -17,11 +17,18 @@
300
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.talon_2dof.ros2_control.xacro b/src/description/ros2_control/arm/arm.talon_2dof.ros2_control.xacro
index d2372c73..d4ae7274 100644
--- a/src/description/ros2_control/arm/arm.talon_2dof.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.talon_2dof.ros2_control.xacro
@@ -12,7 +12,7 @@
10
5
0
- can0
+ can1
@@ -32,8 +32,12 @@
0.0
+
+
+
+
diff --git a/src/description/ros2_control/arm/arm.talon_3dof.ros2_control.xacro b/src/description/ros2_control/arm/arm.talon_3dof.ros2_control.xacro
index c795063a..392d4e55 100644
--- a/src/description/ros2_control/arm/arm.talon_3dof.ros2_control.xacro
+++ b/src/description/ros2_control/arm/arm.talon_3dof.ros2_control.xacro
@@ -11,7 +11,7 @@
10
5
0
- can0
+ can1
@@ -30,10 +30,12 @@
0.0
+
+
@@ -51,10 +53,12 @@
0.0
+
+
@@ -72,10 +76,12 @@
0.0
+
+
@@ -94,8 +100,12 @@
0.0
+
+
+
+
diff --git a/src/description/ros2_control/drive/drive.led.ros2_control.xacro b/src/description/ros2_control/drive/drive.led.ros2_control.xacro
index b560b895..24628c1d 100644
--- a/src/description/ros2_control/drive/drive.led.ros2_control.xacro
+++ b/src/description/ros2_control/drive/drive.led.ros2_control.xacro
@@ -1,19 +1,27 @@
-
+
led_ros2_control/LEDHardwareInterface
- ${gpio_pin}
- ${default_state}
+ ${can_interface}
+ 0x120
+ 10
+ 5
+ 0
-
-
-
+ true
+ 0
+
+
+
+
+
+
diff --git a/src/description/ros2_control/drive/drive.odrive.ros2_control.xacro b/src/description/ros2_control/drive/drive.odrive.ros2_control.xacro
index 2551097d..ec25c66b 100644
--- a/src/description/ros2_control/drive/drive.odrive.ros2_control.xacro
+++ b/src/description/ros2_control/drive/drive.odrive.ros2_control.xacro
@@ -1,12 +1,21 @@
-
+
- odrive_ros2_control_plugin/ODriveHardwareInterface
- can1
+ ${deactivate_odrive}
+
+ mock_components/GenericSystem
+
+
+ odrive_ros2_control_plugin/ODriveHardwareInterface
+ 30
+ 5
+ 0
+ can1
+
@@ -14,10 +23,13 @@
26
-
- 0.0
-
+
+
+
+
+
@@ -25,10 +37,13 @@
26
-
- 0.0
-
+
+
+
+
+
diff --git a/src/description/ros2_control/drive/drive.killswitch.ros2_control.xacro b/src/description/ros2_control/drive/drive.power_module.ros2_control.xacro
similarity index 53%
rename from src/description/ros2_control/drive/drive.killswitch.ros2_control.xacro
rename to src/description/ros2_control/drive/drive.power_module.ros2_control.xacro
index 2ee52595..8f6c90bc 100644
--- a/src/description/ros2_control/drive/drive.killswitch.ros2_control.xacro
+++ b/src/description/ros2_control/drive/drive.power_module.ros2_control.xacro
@@ -1,23 +1,29 @@
-
+
- killswitch_ros2_control/KillswitchHardwareInterface
+ power_module_ros2_control/PowerModuleHardwareInterface
${can_interface}
0x100
+ 10
+ 5
+ 0
+
-
-
+
+
+
+
diff --git a/src/description/ros2_control/drive/drive.rmd.ros2_control.xacro b/src/description/ros2_control/drive/drive.rmd.ros2_control.xacro
index 9b2a21bf..593bd109 100644
--- a/src/description/ros2_control/drive/drive.rmd.ros2_control.xacro
+++ b/src/description/ros2_control/drive/drive.rmd.ros2_control.xacro
@@ -15,12 +15,18 @@
1
-1.0
150
-
+
+
+
+
+
+
+
@@ -28,12 +34,18 @@
1
1.0
150
-
+
+
+
+
+
+
+
@@ -41,12 +53,18 @@
1
1.0
150
-
+
+
+
+
+
+
+
@@ -54,12 +72,18 @@
1
-1.0
150
-
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/science/science.dc.ros2_control.xacro b/src/description/ros2_control/science/science.dc.ros2_control.xacro
new file mode 100644
index 00000000..49687248
--- /dev/null
+++ b/src/description/ros2_control/science/science.dc.ros2_control.xacro
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+ dc_ros2_control/DCHardwareInterface
+ 20
+ 5
+ 0
+ can0
+ 0x70
+
+
+
+ 0
+ revolute
+
+ 50.9
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ revolute
+
+ 50.9
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/science/science.laser.ros2_control.xacro b/src/description/ros2_control/science/science.laser.ros2_control.xacro
index ee7f97c7..6fea9233 100644
--- a/src/description/ros2_control/science/science.laser.ros2_control.xacro
+++ b/src/description/ros2_control/science/science.laser.ros2_control.xacro
@@ -9,13 +9,18 @@
laser_ros2_control/LaserHardwareInterface
${can_interface}
0x130
+ 10
+ 5
+ 0
+
+
@@ -23,4 +28,3 @@
-
diff --git a/src/description/ros2_control/science/science.led.ros2_control.xacro b/src/description/ros2_control/science/science.led.ros2_control.xacro
new file mode 100644
index 00000000..4b583700
--- /dev/null
+++ b/src/description/ros2_control/science/science.led.ros2_control.xacro
@@ -0,0 +1,23 @@
+
+
+
+
+
+ led_ros2_control/LEDHardwareInterface
+ can0
+ 0x110
+ 10
+ 5
+ 0
+
+
+
+ false
+ 0
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/science/science.limit_switch.ros2_control.xacro b/src/description/ros2_control/science/science.limit_switch.ros2_control.xacro
new file mode 100644
index 00000000..698ced10
--- /dev/null
+++ b/src/description/ros2_control/science/science.limit_switch.ros2_control.xacro
@@ -0,0 +1,21 @@
+
+
+
+
+
+ limit_switch_ros2_control/LimitSwitchHardwareInterface
+ can0
+ 0x100
+ 10
+ 5
+ 0
+
+
+
+ 0
+
+
+
+
+
+
diff --git a/src/description/ros2_control/science/science.mock.ros2_control.xacro b/src/description/ros2_control/science/science.mock.ros2_control.xacro
index f1686fcc..37987cf4 100644
--- a/src/description/ros2_control/science/science.mock.ros2_control.xacro
+++ b/src/description/ros2_control/science/science.mock.ros2_control.xacro
@@ -55,13 +55,13 @@
-
+
-
+
diff --git a/src/description/ros2_control/science/science.servo.ros2_control.xacro b/src/description/ros2_control/science/science.servo.ros2_control.xacro
index c441e9b5..d20d1578 100644
--- a/src/description/ros2_control/science/science.servo.ros2_control.xacro
+++ b/src/description/ros2_control/science/science.servo.ros2_control.xacro
@@ -6,7 +6,7 @@
servo_ros2_control/SERVOHardwareInterface
20
5
- 1
+ 0
can0
0x80
@@ -20,8 +20,14 @@
revolute
+
+
+
+
+
+
@@ -33,8 +39,14 @@
revolute
+
+
+
+
+
+
@@ -45,7 +57,14 @@
revolute
+
+
+
+
+
+
+
@@ -56,7 +75,14 @@
revolute
+
+
+
+
+
+
+
@@ -68,8 +94,14 @@
revolute
+
+
+
+
+
+
@@ -81,8 +113,14 @@
revolute
+
+
+
+
+
+
@@ -94,7 +132,14 @@
revolute
+
+
+
+
+
+
+
diff --git a/src/description/ros2_control/science/science.stepper.ros2_control.xacro b/src/description/ros2_control/science/science.stepper.ros2_control.xacro
index 0b4e8e57..8d8fc00f 100644
--- a/src/description/ros2_control/science/science.stepper.ros2_control.xacro
+++ b/src/description/ros2_control/science/science.stepper.ros2_control.xacro
@@ -7,39 +7,49 @@
stepper_ros2_control/STEPPERHardwareInterface
+ 10
+ 5
+ 0
+ can0
+ 0x90
+ 0x0
+ 1
+ 20
+ continuous
+
+
+
+
+
- 0.0
-
-
- 1
-
-
- 1
-
+
+
+ 0x1
+ 1
+ 20
+ continuous
+
+
+
+
+
- 0.0
-
-
- 1
-
-
- 1
-
+
-
+
\ No newline at end of file
diff --git a/src/description/ros2_control/science/science.talon.ros2_control.xacro b/src/description/ros2_control/science/science.talon.ros2_control.xacro
index 7d9b663c..5629a1da 100644
--- a/src/description/ros2_control/science/science.talon.ros2_control.xacro
+++ b/src/description/ros2_control/science/science.talon.ros2_control.xacro
@@ -30,10 +30,12 @@
0.0
+
+
@@ -50,10 +52,12 @@
0.0
+
+
diff --git a/src/description/urdf/athena_arm.urdf.xacro b/src/description/urdf/athena_arm.urdf.xacro
index f7691179..3d4453d8 100644
--- a/src/description/urdf/athena_arm.urdf.xacro
+++ b/src/description/urdf/athena_arm.urdf.xacro
@@ -32,6 +32,13 @@
+
+
+
+
+
+
+
@@ -59,7 +66,10 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/src/description/urdf/athena_drive.urdf.xacro b/src/description/urdf/athena_drive.urdf.xacro
index 66e8d7a2..11119ab5 100644
--- a/src/description/urdf/athena_drive.urdf.xacro
+++ b/src/description/urdf/athena_drive.urdf.xacro
@@ -3,11 +3,11 @@
-
+
@@ -39,8 +39,8 @@
-
-
+
+
@@ -70,14 +70,18 @@
simulation_controllers="$(arg simulation_controllers)"/>
-
+
+
+
+
+
-
+
-
-
+
+
diff --git a/src/description/urdf/athena_science.urdf.xacro b/src/description/urdf/athena_science.urdf.xacro
index b81b61e3..92b023a4 100644
--- a/src/description/urdf/athena_science.urdf.xacro
+++ b/src/description/urdf/athena_science.urdf.xacro
@@ -6,10 +6,14 @@
-
-
+
+
+
+
+
+
@@ -30,11 +34,18 @@
+
+
+
-
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/hardware_interfaces/README.md b/src/hardware_interfaces/README.md
index a083d586..60fea846 100644
--- a/src/hardware_interfaces/README.md
+++ b/src/hardware_interfaces/README.md
@@ -4,7 +4,7 @@ This directory contains ROS2 Control hardware interface implementations for vari
## Available Interfaces
-- `killswitch_ros2_control/` - Killswitch hardware interface
+- `power_module_ros2_control/` - PowerModule hardware interface
- `laser_ros2_control/` - Laser hardware interface
- `led_ros2_control/` - LED hardware interface
- `rmd_ros2_control/` - RMD motor controller interface
diff --git a/src/hardware_interfaces/dc_ros2_control/CMakeLists.txt b/src/hardware_interfaces/dc_ros2_control/CMakeLists.txt
new file mode 100644
index 00000000..4c11c388
--- /dev/null
+++ b/src/hardware_interfaces/dc_ros2_control/CMakeLists.txt
@@ -0,0 +1,70 @@
+cmake_minimum_required(VERSION 3.8)
+project(dc_ros2_control)
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+set(THIS_PACKAGE_INCLUDE_DEPENDS
+ hardware_interface
+ pluginlib
+ rclcpp
+ rclcpp_lifecycle
+ umdloop_can_library
+)
+
+find_package(ament_cmake REQUIRED)
+foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
+ find_package(${Dependency} REQUIRED)
+endforeach()
+
+include_directories(include)
+
+add_library(dc_ros2_control SHARED
+ src/dc_hardware_interface.cpp
+)
+
+target_compile_features(dc_ros2_control PUBLIC cxx_std_20)
+
+target_include_directories(dc_ros2_control PUBLIC
+ $
+ $
+)
+
+# Link umdloop_can_library directly via CMake target (same as servo HWI)
+target_link_libraries(dc_ros2_control PUBLIC umdloop_can_library::umdloop_can_library)
+
+ament_target_dependencies(dc_ros2_control PUBLIC
+ hardware_interface
+ pluginlib
+ rclcpp
+ rclcpp_lifecycle
+)
+
+# Export hardware plugin
+pluginlib_export_plugin_description_file(hardware_interface dc_hardware_interface.xml)
+
+# Install
+install(
+ DIRECTORY include/
+ DESTINATION include/dc_ros2_control
+)
+
+install(TARGETS dc_ros2_control
+ EXPORT export_dc_ros2_control
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ RUNTIME DESTINATION bin
+)
+
+if(BUILD_TESTING)
+ find_package(ament_lint_auto REQUIRED)
+ set(ament_cmake_copyright_FOUND TRUE)
+ set(ament_cmake_cpplint_FOUND TRUE)
+ ament_lint_auto_find_test_dependencies()
+endif()
+
+ament_export_targets(export_dc_ros2_control)
+ament_export_include_directories(include)
+ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS})
+ament_package()
\ No newline at end of file
diff --git a/src/hardware_interfaces/dc_ros2_control/dc_hardware_interface.xml b/src/hardware_interfaces/dc_ros2_control/dc_hardware_interface.xml
new file mode 100644
index 00000000..76bd8b7e
--- /dev/null
+++ b/src/hardware_interfaces/dc_ros2_control/dc_hardware_interface.xml
@@ -0,0 +1,9 @@
+
+
+
+ UMDLoop's ros2_control plugin for the DC motors
+
+
+
\ No newline at end of file
diff --git a/src/hardware_interfaces/dc_ros2_control/include/dc_ros2_control/dc_hardware_interface.hpp b/src/hardware_interfaces/dc_ros2_control/include/dc_ros2_control/dc_hardware_interface.hpp
new file mode 100644
index 00000000..a9f9e196
--- /dev/null
+++ b/src/hardware_interfaces/dc_ros2_control/include/dc_ros2_control/dc_hardware_interface.hpp
@@ -0,0 +1,322 @@
+#ifndef DC_HARDWARE_INTERFACE_HPP_
+#define DC_HARDWARE_INTERFACE_HPP_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hardware_interface/handle.hpp"
+#include "hardware_interface/hardware_info.hpp"
+#include "hardware_interface/system_interface.hpp"
+#include "hardware_interface/types/hardware_interface_return_values.hpp"
+#include "rclcpp/macros.hpp"
+#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
+#include "rclcpp_lifecycle/state.hpp"
+
+#include "umdloop_can_library/SocketCanBus.hpp"
+#include "umdloop_can_library/CanFrame.hpp"
+
+namespace CANLib
+{
+struct CanFrame;
+}
+
+namespace dc_ros2_control
+{
+class DCHardwareInterface : public hardware_interface::SystemInterface
+{
+public:
+ RCLCPP_SHARED_PTR_DEFINITIONS(DCHardwareInterface)
+
+ // Lifecycle
+ hardware_interface::CallbackReturn on_init(
+ const hardware_interface::HardwareInfo & info) override;
+
+ std::vector export_state_interfaces() override;
+ std::vector export_command_interfaces() override;
+
+ hardware_interface::CallbackReturn on_configure(
+ const rclcpp_lifecycle::State & previous_state) override;
+
+ hardware_interface::CallbackReturn on_cleanup(
+ const rclcpp_lifecycle::State & previous_state) override;
+
+ hardware_interface::CallbackReturn on_activate(
+ const rclcpp_lifecycle::State & previous_state) override;
+
+ hardware_interface::CallbackReturn on_deactivate(
+ const rclcpp_lifecycle::State & previous_state) override;
+
+ hardware_interface::CallbackReturn on_shutdown(
+ const rclcpp_lifecycle::State & previous_state) override;
+
+ hardware_interface::return_type perform_command_mode_switch(
+ const std::vector& start_interfaces,
+ const std::vector& stop_interfaces) override;
+
+ hardware_interface::return_type read(
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
+
+ hardware_interface::return_type write(
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
+
+ // -- Helper Functions --
+ void on_can_message(const CANLib::CanFrame& frame);
+ void logger_function();
+
+ // Unit conversions
+ double calculate_joint_position_from_motor_position(double motor_pos_deg, int gear_ratio);
+ double calculate_joint_displacement_from_motor_position(double motor_pos_deg, int gear_ratio, double dist_per_rev);
+ double calculate_joint_angular_velocity_from_motor_velocity(double motor_vel_dps, int gear_ratio);
+ double calculate_joint_linear_velocity_from_motor_velocity(double motor_vel_dps, int gear_ratio, double dist_per_rev);
+
+ int16_t calculate_motor_position_from_desired_joint_position(double joint_position, int gear_ratio);
+ int16_t calculate_motor_position_from_desired_joint_displacement(double joint_position, int gear_ratio, double dist_per_rev);
+ int16_t calculate_motor_velocity_from_desired_joint_angular_velocity(double joint_velocity, int gear_ratio);
+ int16_t calculate_motor_velocity_from_desired_joint_linear_velocity(double joint_velocity, int gear_ratio, double dist_per_rev);
+
+ struct DecodedCommand
+ {
+ uint8_t command_id;
+ std::vector u8_data;
+ std::vector i16_data;
+ std::vector i32_data;
+ };
+
+ void format_control_command(CANLib::CanFrame & frame, size_t joint_index);
+ bool format_status_command(CANLib::CanFrame & frame, uint8_t command_id, uint8_t node_id);
+ bool format_maintenance_command(
+ CANLib::CanFrame & frame, uint8_t node_id, const DecodedCommand & decoded_cmd);
+
+private:
+ // Hardware Interface Parameters
+ int update_rate;
+ double elapsed_update_time;
+ double elapsed_time;
+ double elapsed_logger_time;
+ int logger_rate;
+ int logger_state;
+ int write_count;
+
+ int num_joints;
+
+ // Stores Arbitration IDs
+ int can_command_id;
+ uint32_t can_response_id;
+
+ // Joint state (read from hardware)
+ std::vector joint_state_position_;
+ std::vector joint_state_velocity_;
+
+ // Previous joint state (change detection)
+ std::vector prev_joint_state_position_;
+ std::vector prev_joint_state_velocity_;
+
+ // Joint commands (written to hardware)
+ std::vector joint_command_position_;
+ std::vector joint_command_velocity_;
+
+ // Previous joint commands (change detection)
+ std::vector prev_joint_command_position_;
+ std::vector prev_joint_command_velocity_;
+
+ // Raw motor data from CAN bus (populated in on_can_message, consumed in read)
+ std::vector motor_position;
+ std::vector motor_velocity;
+ std::vector device_status;
+
+ // Telemetry data (TODO: implement CAN telemetry commands)
+ std::vector motor_temperature_;
+ std::vector motor_torque_current_;
+ std::vector motor_status_;
+
+ std::vector motor_status_req_;
+ std::vector motor_maintenance_req_;
+ std::vector maintenance_frame_high_;
+ std::vector maintenance_frame_low_;
+ std::vector maintenance_frame_;
+ std::vector maintenance_data_count_;
+ std::vector> decoded_maintenance_frame_;
+
+ std::vector prev_status_req_;
+ std::vector prev_maintenance_req_;
+ std::vector elapsed_status_request_time_;
+ std::vector elapsed_maintenance_request_time_;
+
+ // Maximum displacement for prismatic joints
+ std::vector max_disp;
+
+ // Prismatic-specific: distance per revolution (future use)
+ std::vector distance_per_rev;
+
+ // CAN Library
+ CANLib::SocketCanBus canBus;
+ CANLib::CanFrame can_tx_frame_;
+ CANLib::CanFrame can_rx_frame_;
+ std::string can_interface;
+
+ // Per-joint parameters (read from XACRO)
+ std::vector joint_node_ids;
+ std::vector joint_gear_ratios;
+ std::vector joint_inverted;
+
+ // Control mode
+ enum class integration_level_t : std::uint8_t
+ {
+ UNDEFINED = 0,
+ POSITION = 1,
+ VELOCITY = 2,
+ };
+
+ std::vector control_level_;
+
+ // Joint type
+ enum class joint_type_t : std::uint8_t
+ {
+ REVOLUTE = 0,
+ PRISMATIC = 1,
+ };
+
+ std::vector joint_type_;
+
+ struct DCJoint
+ {
+ std::string name;
+ uint8_t node_id;
+ int gear_ratio;
+ bool inverted;
+ double max_disp;
+ double distance_per_rev;
+ integration_level_t control_level;
+ joint_type_t joint_type;
+
+ double joint_state_position;
+ double joint_state_velocity;
+ double motor_position;
+ double motor_velocity;
+ double motor_status;
+ double motor_temperature;
+ double motor_torque_current;
+
+ double joint_command_position;
+ double joint_command_velocity;
+ double motor_status_req;
+ double motor_maintenance_req;
+ double maintenance_frame_high;
+ double maintenance_frame_low;
+ double maintenance_frame;
+ double maintenance_data_count;
+ std::vector decoded_maintenance_frame;
+
+ double prev_status_req;
+ double prev_maintenance_req;
+ double elapsed_status_request_time;
+ double elapsed_maintenance_request_time;
+ double prev_joint_command_position;
+ double prev_joint_command_velocity;
+ int device_status;
+
+ std::vector state_interface_names;
+ std::vector command_interface_names;
+ std::unordered_map parameters;
+ };
+
+ std::vector DCJoints_;
+
+ enum class ControlCommands : uint8_t
+ {
+ ABSOLUTE_POS_CONTROL_CMD = 0x20,
+ VELOCITY_CONTROL_CMD = 0x30,
+ };
+
+ enum class MaintenanceCommands : uint8_t
+ {
+ PCB_HEARTBEAT_CMD = 0x10,
+ MAINTENANCE_CMD = 0x60,
+ DC_SPECS_CMD = 0x70,
+ };
+
+ enum class MaintenanceCommandOptions : uint8_t
+ {
+ SET_CURRENT_MULTI_TURN_POS_ZERO_TO_ROM_CMD = 0x00,
+ REQUEST_VECTORS_CMD = 0x01,
+ MOTOR_STOP_CMD = 0x02,
+ MOTOR_SHUTDOWN_CMD = 0x03,
+ CLEAR_ERRORS_CMD = 0x04,
+ };
+
+ enum class StatusCommands : uint8_t
+ {
+ MOTOR_STATE = 0x40,
+ MOTOR_STATUS = 0x50,
+ };
+
+ enum class MotorStatus : uint8_t
+ {
+ UNDEFINED = 0,
+ IDLE = 1,
+ STARTUP_SEQUENCE = 2,
+
+ ERROR_INVALID_REQUEST = 3,
+ ERROR_MOTOR_DISARMED = 4,
+ ERROR_MOTOR_FAILED = 5,
+ ERROR_CONTROLLER_FAILED = 6,
+ ERROR_ESTOP_REQUESTED = 7,
+ ERROR_UNKNOWN_POSITION = 8,
+
+ POSITION_CONTROL = 9,
+ VELOCITY_CONTROL = 10,
+ MOTOR_STOPPED = 11
+ };
+
+ enum class ValidateRequest : uint8_t
+ {
+ INVALID = 0,
+ VALID = 1,
+ };
+
+ static constexpr std::array kStatusCommands = {
+ StatusCommands::MOTOR_STATE,
+ StatusCommands::MOTOR_STATUS,
+ };
+
+ inline DecodedCommand unpack_command_full(int32_t counts_in, int64_t payload_in)
+ {
+ const uint32_t counts = static_cast(counts_in);
+ const uint64_t payload = static_cast(payload_in);
+
+ const uint8_t u8_count = static_cast((counts >> 16) & 0xFF);
+ const uint8_t i16_count = static_cast((counts >> 8) & 0xFF);
+ const uint8_t i32_count = static_cast(counts & 0xFF);
+
+ int bit_offset = 64;
+ auto pop_bits = [&](int bits) -> uint64_t {
+ bit_offset -= bits;
+ const uint64_t mask = (bits == 64) ? std::numeric_limits::max() :
+ ((1ULL << bits) - 1ULL);
+ return (payload >> bit_offset) & mask;
+ };
+
+ DecodedCommand result{};
+ result.command_id = static_cast(pop_bits(8));
+ for (uint8_t i = 0; i < u8_count; ++i) {
+ result.u8_data.push_back(static_cast(pop_bits(8)));
+ }
+ for (uint8_t i = 0; i < i16_count; ++i) {
+ result.i16_data.push_back(static_cast(static_cast(pop_bits(16))));
+ }
+ for (uint8_t i = 0; i < i32_count; ++i) {
+ result.i32_data.push_back(static_cast(static_cast(pop_bits(32))));
+ }
+ return result;
+ }
+};
+
+} // namespace dc_ros2_control
+
+#endif // DC_HARDWARE_INTERFACE_HPP_
diff --git a/src/hardware_interfaces/dc_ros2_control/package.xml b/src/hardware_interfaces/dc_ros2_control/package.xml
new file mode 100644
index 00000000..6f174cc8
--- /dev/null
+++ b/src/hardware_interfaces/dc_ros2_control/package.xml
@@ -0,0 +1,35 @@
+
+
+
+ dc_ros2_control
+ 0.0.0
+ TODO: Package description
+ henry
+ TODO: License declaration
+
+ ament_cmake
+ ament_cmake
+
+ hardware_interface
+ pluginlib
+ rclcpp
+ rclcpp_lifecycle
+ realtime_tools
+ msgs
+
+
+ umdloop_can_library
+ umdloop_can_library
+
+ ament_lint_auto
+ ament_lint_common
+
+ ament_cmake
+ rosidl_default_runtime
+
+ rosidl_interface_packages
+
+
+ ament_cmake
+
+
\ No newline at end of file
diff --git a/src/hardware_interfaces/dc_ros2_control/src/dc_hardware_interface.cpp b/src/hardware_interfaces/dc_ros2_control/src/dc_hardware_interface.cpp
new file mode 100644
index 00000000..0c505a78
--- /dev/null
+++ b/src/hardware_interfaces/dc_ros2_control/src/dc_hardware_interface.cpp
@@ -0,0 +1,837 @@
+#include "dc_ros2_control/dc_hardware_interface.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hardware_interface/system_interface.hpp"
+#include "hardware_interface/types/hardware_interface_type_values.hpp"
+#include "rclcpp/rclcpp.hpp"
+
+namespace dc_ros2_control
+{
+
+// =============================================================================
+// Unit Conversions (matches servo HWI structure)
+// =============================================================================
+
+double DCHardwareInterface::calculate_joint_position_from_motor_position(double motor_pos_deg, int gear_ratio) {
+ // deg -> radians with gear ratio
+ return (motor_pos_deg * (M_PI / 180.0)) / gear_ratio;
+}
+
+double DCHardwareInterface::calculate_joint_displacement_from_motor_position(double motor_pos_deg, int gear_ratio, double dist_per_rev) {
+ // deg -> meters: (deg / 360) * distance_per_rev / gear_ratio
+ return (motor_pos_deg / 360.0) * dist_per_rev / gear_ratio;
+}
+
+double DCHardwareInterface::calculate_joint_angular_velocity_from_motor_velocity(double motor_vel_dps, int gear_ratio) {
+ // deg/s -> rad/s with gear ratio
+ return (motor_vel_dps * (M_PI / 180.0)) / gear_ratio;
+}
+
+double DCHardwareInterface::calculate_joint_linear_velocity_from_motor_velocity(double motor_vel_dps, int gear_ratio, double dist_per_rev) {
+ // deg/s -> m/s
+ return (motor_vel_dps / 360.0) * dist_per_rev / gear_ratio;
+}
+
+int16_t DCHardwareInterface::calculate_motor_position_from_desired_joint_position(double joint_position, int gear_ratio) {
+ // radians -> deg with gear ratio
+ return static_cast(std::round((joint_position * (180.0 / M_PI)) * gear_ratio));
+}
+
+int16_t DCHardwareInterface::calculate_motor_position_from_desired_joint_displacement(double joint_position, int gear_ratio, double dist_per_rev) {
+ // meters -> deg: (m / distance_per_rev) * 360 * gear_ratio
+ return static_cast(std::round((joint_position / dist_per_rev) * 360.0 * gear_ratio));
+}
+
+int16_t DCHardwareInterface::calculate_motor_velocity_from_desired_joint_angular_velocity(double joint_velocity, int gear_ratio) {
+ // rad/s -> deg/s with gear ratio
+ return static_cast(std::round((joint_velocity * (180.0 / M_PI)) * gear_ratio));
+}
+
+int16_t DCHardwareInterface::calculate_motor_velocity_from_desired_joint_linear_velocity(double joint_velocity, int gear_ratio, double dist_per_rev) {
+ // m/s -> deg/s
+ return static_cast(std::round((joint_velocity / dist_per_rev) * 360.0 * gear_ratio));
+}
+
+void DCHardwareInterface::format_control_command(CANLib::CanFrame & frame, size_t joint_index)
+{
+ std::fill(std::begin(frame.data), std::end(frame.data), 0x00);
+
+ auto & joint = DCJoints_[joint_index];
+ const double dir = joint.inverted ? -1.0 : 1.0;
+
+ if (
+ joint.control_level == integration_level_t::POSITION &&
+ std::isfinite(joint.joint_command_position) &&
+ joint.joint_command_position != joint.prev_joint_command_position)
+ {
+ joint.prev_joint_command_position = joint.joint_command_position;
+ if (joint.joint_type == joint_type_t::PRISMATIC) {
+ joint.joint_command_position = std::clamp(
+ joint.joint_command_position, 0.0, joint.max_disp);
+ }
+
+ int16_t motor_pos = 0;
+ if (joint.joint_type == joint_type_t::REVOLUTE) {
+ motor_pos = calculate_motor_position_from_desired_joint_position(
+ dir * joint.joint_command_position, joint.gear_ratio);
+ } else {
+ motor_pos = calculate_motor_position_from_desired_joint_displacement(
+ dir * joint.joint_command_position, joint.gear_ratio, joint.distance_per_rev);
+ }
+
+ frame.dlc = 3;
+ frame.data[0] = static_cast(
+ static_cast(ControlCommands::ABSOLUTE_POS_CONTROL_CMD) + joint.node_id);
+ frame.data[1] = static_cast(motor_pos & 0xFF);
+ frame.data[2] = static_cast((motor_pos >> 8) & 0xFF);
+ return;
+ }
+
+ if (
+ joint.control_level == integration_level_t::VELOCITY &&
+ std::isfinite(joint.joint_command_velocity) &&
+ joint.joint_command_velocity != joint.prev_joint_command_velocity)
+ {
+ joint.prev_joint_command_velocity = joint.joint_command_velocity;
+ int16_t motor_vel = 0;
+ if (joint.joint_type == joint_type_t::REVOLUTE) {
+ motor_vel = calculate_motor_velocity_from_desired_joint_angular_velocity(
+ dir * joint.joint_command_velocity, joint.gear_ratio);
+ } else {
+ motor_vel = calculate_motor_velocity_from_desired_joint_linear_velocity(
+ dir * joint.joint_command_velocity, joint.gear_ratio, joint.distance_per_rev);
+ }
+
+ frame.dlc = 3;
+ frame.data[0] = static_cast(
+ static_cast(ControlCommands::VELOCITY_CONTROL_CMD) + joint.node_id);
+ frame.data[1] = static_cast(motor_vel & 0xFF);
+ frame.data[2] = static_cast((motor_vel >> 8) & 0xFF);
+ return;
+ }
+
+ frame.dlc = 2;
+ frame.data[0] = static_cast(
+ static_cast(StatusCommands::MOTOR_STATE) + joint.node_id);
+ frame.data[1] = static_cast(ValidateRequest::VALID);
+}
+
+bool DCHardwareInterface::format_status_command(
+ CANLib::CanFrame & frame, uint8_t command_id, uint8_t node_id)
+{
+ std::fill(std::begin(frame.data), std::end(frame.data), 0x00);
+ frame.dlc = 2;
+ switch (static_cast(command_id)) {
+ case StatusCommands::MOTOR_STATE:
+ frame.data[0] = static_cast(static_cast(StatusCommands::MOTOR_STATE) + node_id);
+ frame.data[1] = static_cast(ValidateRequest::VALID);
+ return true;
+ case StatusCommands::MOTOR_STATUS:
+ frame.data[0] = static_cast(static_cast(StatusCommands::MOTOR_STATUS) + node_id);
+ frame.data[1] = static_cast(ValidateRequest::VALID);
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool DCHardwareInterface::format_maintenance_command(
+ CANLib::CanFrame & frame, uint8_t node_id, const DecodedCommand & decoded_cmd)
+{
+ std::fill(std::begin(frame.data), std::end(frame.data), 0x00);
+ frame.dlc = 2;
+ frame.data[0] = static_cast(
+ static_cast(MaintenanceCommands::MAINTENANCE_CMD) + node_id);
+ frame.data[1] = decoded_cmd.command_id;
+
+ switch (static_cast(decoded_cmd.command_id)) {
+ case MaintenanceCommands::PCB_HEARTBEAT_CMD:
+ if (decoded_cmd.u8_data.size() != 1 || !decoded_cmd.i16_data.empty() ||
+ !decoded_cmd.i32_data.empty())
+ {
+ return false;
+ }
+ frame.data[0] = static_cast(MaintenanceCommands::PCB_HEARTBEAT_CMD);
+ frame.data[1] = decoded_cmd.u8_data[0];
+ return true;
+ case MaintenanceCommands::MAINTENANCE_CMD:
+ if (decoded_cmd.u8_data.size() != 1 || !decoded_cmd.i16_data.empty() ||
+ !decoded_cmd.i32_data.empty())
+ {
+ return false;
+ }
+ frame.data[0] = static_cast(static_cast(MaintenanceCommands::MAINTENANCE_CMD) + node_id);
+ frame.data[1] = decoded_cmd.u8_data[0];
+ return true;
+ case MaintenanceCommands::DC_SPECS_CMD:
+ if (decoded_cmd.u8_data.size() != 1 || !decoded_cmd.i16_data.empty() ||
+ !decoded_cmd.i32_data.empty())
+ {
+ return false;
+ }
+ frame.data[0] = static_cast(static_cast(MaintenanceCommands::DC_SPECS_CMD) + node_id);
+ frame.data[1] = static_cast(ValidateRequest::VALID);
+ return true;
+ default:
+ return false;
+ }
+}
+
+// =============================================================================
+// Logger
+// =============================================================================
+
+void DCHardwareInterface::logger_function()
+{
+ if (num_joints == 0) return;
+
+ std::string log_msg = "\033[2J\033[H \nDC Motor Logger";
+ std::ostringstream oss;
+ std::string control_mode;
+ std::string joint_type_str;
+ std::string status;
+
+ oss << "\n--- HWI Specific ---\n"
+ << "CAN Interface: " << can_interface
+ << " | Command CAN ID: 0x" << std::hex << std::uppercase << can_command_id
+ << " | Response CAN ID: 0x" << std::hex << std::uppercase << can_response_id
+ << " | HWI Update Rate: " << std::dec << update_rate
+ << " | Logger Update Rate: " << logger_rate << "\n"
+ << "Elapsed Time since first update: " << elapsed_time << "\n"
+ << "\n--- Joint Specific ---";
+
+ for (int i = 0; i < num_joints; i++) {
+ const auto & joint = DCJoints_[static_cast(i)];
+ switch (joint.control_level) {
+ case integration_level_t::POSITION: control_mode = "POSITION"; break;
+ case integration_level_t::VELOCITY: control_mode = "VELOCITY"; break;
+ default: control_mode = "UNDEFINED"; break;
+ }
+
+ switch (joint.joint_type) {
+ case joint_type_t::REVOLUTE: joint_type_str = "REVOLUTE"; break;
+ case joint_type_t::PRISMATIC: joint_type_str = "PRISMATIC"; break;
+ }
+
+ switch (joint.device_status) {
+ case static_cast(MotorStatus::UNDEFINED): status = "Undefined"; break;
+ case static_cast(MotorStatus::IDLE): status = "Idle"; break;
+ case static_cast(MotorStatus::STARTUP_SEQUENCE): status = "Startup Sequence"; break;
+ case static_cast(MotorStatus::ERROR_INVALID_REQUEST): status = "Error (Invalid Request)"; break;
+ case static_cast(MotorStatus::ERROR_MOTOR_DISARMED): status = "Error (Motor Disarmed)"; break;
+ case static_cast(MotorStatus::ERROR_MOTOR_FAILED): status = "Error (Motor Failed)"; break;
+ case static_cast(MotorStatus::ERROR_CONTROLLER_FAILED): status = "Error (Controller Failed)"; break;
+ case static_cast(MotorStatus::ERROR_ESTOP_REQUESTED): status = "Error (ESTOP Requested)"; break;
+ case static_cast(MotorStatus::ERROR_UNKNOWN_POSITION): status = "Error (Unknown Position)"; break;
+ case static_cast(MotorStatus::POSITION_CONTROL): status = "Position Control"; break;
+ case static_cast(MotorStatus::VELOCITY_CONTROL): status = "Velocity Control"; break;
+ case static_cast(MotorStatus::MOTOR_STOPPED): status = "Motor Stopped"; break;
+ default: status = "UNKNOWN (" + std::to_string(joint.device_status) + ")"; break;
+ }
+
+ oss << "\nJOINT: " << joint.name << "\n"
+ << "Parameters: Node ID: 0x" << std::hex << std::uppercase << static_cast(joint.node_id)
+ << " | Gear Ratio: " << std::dec << joint.gear_ratio
+ << " | Inverted: " << (joint.inverted ? "true" : "false")
+ << " | Device Status: " << status
+ << " | Control Mode: " << control_mode
+ << " | Joint Type: " << joint_type_str << "\n"
+ << "-- Commands --\n"
+ << "Joint Command Position: " << joint.joint_command_position
+ << " | Joint Command Velocity: " << joint.joint_command_velocity << "\n"
+ << "-- State --\n"
+ << "Joint Position: " << joint.joint_state_position
+ << " | Joint Velocity: " << joint.joint_state_velocity << "\n"
+ << "-- Telemetry --\n"
+ << "Temperature: " << joint.motor_temperature << " C"
+ << " | Torque Current: " << joint.motor_torque_current << " A\n";
+ }
+
+ log_msg += oss.str();
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "%s", log_msg.c_str());
+}
+
+// =============================================================================
+// Initialization
+// =============================================================================
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_init(
+ const hardware_interface::HardwareInfo & info)
+{
+ if (hardware_interface::SystemInterface::on_init(info) !=
+ hardware_interface::CallbackReturn::SUCCESS)
+ {
+ return hardware_interface::CallbackReturn::ERROR;
+ }
+
+ DCJoints_.clear();
+ for (const auto & joint : info_.joints) {
+ std::vector state_if_names;
+ for (const auto & si : joint.state_interfaces) {
+ state_if_names.push_back(si.name);
+ }
+
+ std::vector command_if_names;
+ for (const auto & ci : joint.command_interfaces) {
+ command_if_names.push_back(ci.name);
+ }
+
+ std::unordered_map params_map;
+ for (const auto & p : joint.parameters) {
+ params_map.emplace(p.first, p.second);
+ }
+
+ const uint8_t node_id = static_cast(
+ std::clamp(std::stoi(joint.parameters.at("node_id"), nullptr, 0), 0, 15));
+ const int gear_ratio = joint.parameters.count("gear_ratio") ?
+ std::abs(std::stoi(joint.parameters.at("gear_ratio"))) : 1;
+ const bool inverted = joint.parameters.count("inverted") &&
+ joint.parameters.at("inverted") == "true";
+
+ joint_type_t joint_type = joint_type_t::REVOLUTE;
+ double joint_max_disp = std::numeric_limits::quiet_NaN();
+ double joint_distance_per_rev = 0.0;
+ const std::string jtype = joint.parameters.count("joint_type") ?
+ joint.parameters.at("joint_type") : "revolute";
+ if (jtype == "revolute") {
+ joint_type = joint_type_t::REVOLUTE;
+ } else if (
+ jtype == "prismatic" &&
+ joint.parameters.count("max_disp") &&
+ joint.parameters.count("distance_per_rev"))
+ {
+ joint_type = joint_type_t::PRISMATIC;
+ joint_max_disp = std::abs(std::stod(joint.parameters.at("max_disp")));
+ joint_distance_per_rev = std::stod(joint.parameters.at("distance_per_rev"));
+ } else {
+ RCLCPP_ERROR(
+ rclcpp::get_logger("DCHardwareInterface"),
+ "Invalid joint_type for joint %s. Must be 'revolute' or 'prismatic' (with 'max_disp' and 'distance_per_rev').",
+ joint.name.c_str());
+ return hardware_interface::CallbackReturn::ERROR;
+ }
+
+ const integration_level_t initial_mode =
+ jtype == "continuous" ? integration_level_t::VELOCITY : integration_level_t::POSITION;
+
+ DCJoints_.push_back(DCJoint{
+ joint.name,
+ node_id,
+ gear_ratio,
+ inverted,
+ joint_max_disp,
+ joint_distance_per_rev,
+ initial_mode,
+ joint_type,
+ std::numeric_limits::quiet_NaN(),
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ std::numeric_limits::quiet_NaN(),
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ {},
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ std::numeric_limits::quiet_NaN(),
+ std::numeric_limits::quiet_NaN(),
+ static_cast(MotorStatus::UNDEFINED),
+ state_if_names,
+ command_if_names,
+ params_map
+ });
+ }
+
+ // -- Hardware-level parameters --
+ num_joints = static_cast(info_.joints.size());
+ update_rate = std::stoi(info_.hardware_parameters.at("update_rate"));
+ logger_rate = std::stoi(info_.hardware_parameters.at("logger_rate"));
+ logger_state = std::stoi(info_.hardware_parameters.at("logger_state"));
+ can_interface = info_.hardware_parameters.at("can_interface");
+ can_command_id = std::stoi(info_.hardware_parameters.at("can_id"), nullptr, 0);
+ can_response_id = can_command_id + 0x01;
+
+ elapsed_update_time = 0.0;
+ elapsed_time = 0.0;
+ elapsed_logger_time = 0.0;
+ write_count = 0;
+
+ return hardware_interface::CallbackReturn::SUCCESS;
+}
+
+// =============================================================================
+// Lifecycle
+// =============================================================================
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_shutdown(
+ const rclcpp_lifecycle::State & previous_state)
+{
+ return this->on_cleanup(previous_state);
+}
+
+std::vector
+DCHardwareInterface::export_state_interfaces()
+{
+ std::vector state_interfaces;
+
+ for (size_t i = 0; i < DCJoints_.size(); i++) {
+ auto & joint = DCJoints_[i];
+ for (const auto & iface : joint.state_interface_names) {
+ double * value = nullptr;
+ if (iface == hardware_interface::HW_IF_POSITION) {
+ value = &joint.joint_state_position;
+ } else if (iface == hardware_interface::HW_IF_VELOCITY) {
+ value = &joint.joint_state_velocity;
+ } else if (iface == "status") {
+ value = &joint.motor_status;
+ } else if (iface == "motor_temperature") {
+ value = &joint.motor_temperature;
+ } else if (iface == "torque_current") {
+ value = &joint.motor_torque_current;
+ }
+
+ if (value == nullptr) {
+ continue;
+ }
+ state_interfaces.emplace_back(joint.name, iface, value);
+ }
+ }
+
+ return state_interfaces;
+}
+
+std::vector
+DCHardwareInterface::export_command_interfaces()
+{
+ std::vector command_interfaces;
+
+ for (size_t i = 0; i < DCJoints_.size(); i++) {
+ auto & joint = DCJoints_[i];
+ for (const auto & iface : joint.command_interface_names) {
+ double * value = nullptr;
+ if (iface == hardware_interface::HW_IF_POSITION) {
+ value = &joint.joint_command_position;
+ } else if (iface == hardware_interface::HW_IF_VELOCITY) {
+ value = &joint.joint_command_velocity;
+ } else if (iface == "status_request") {
+ value = &joint.motor_status_req;
+ } else if (iface == "maintenance_request") {
+ value = &joint.motor_maintenance_req;
+ } else if (iface == "maintenance_frame_high") {
+ value = &joint.maintenance_frame_high;
+ } else if (iface == "maintenance_frame_low") {
+ value = &joint.maintenance_frame_low;
+ } else if (iface == "maintenance_data_count") {
+ value = &joint.maintenance_data_count;
+ }
+
+ if (value == nullptr) {
+ continue;
+ }
+ command_interfaces.emplace_back(joint.name, iface, value);
+ }
+ }
+
+ return command_interfaces;
+}
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_configure(
+ const rclcpp_lifecycle::State & /*previous_state*/)
+{
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Configuring DC Motor HWI...");
+
+ if (!canBus.open(can_interface,
+ std::bind(&DCHardwareInterface::on_can_message, this, std::placeholders::_1)))
+ {
+ RCLCPP_ERROR(rclcpp::get_logger("DCHardwareInterface"),
+ "Failed to open CAN interface: %s", can_interface.c_str());
+ return hardware_interface::CallbackReturn::ERROR;
+ }
+
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "CAN interface opened successfully.");
+ return hardware_interface::CallbackReturn::SUCCESS;
+}
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_cleanup(
+ const rclcpp_lifecycle::State & /*previous_state*/)
+{
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Cleaning up... please wait...");
+
+ for (const auto & joint : DCJoints_) {
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ if (format_maintenance_command(
+ frame, joint.node_id,
+ DecodedCommand{
+ static_cast(MaintenanceCommands::MAINTENANCE_CMD),
+ {static_cast(MaintenanceCommandOptions::MOTOR_SHUTDOWN_CMD)},
+ {},
+ {}
+ }))
+ {
+ canBus.send(frame);
+ }
+ }
+
+ canBus.close();
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Cleanup complete.");
+ return hardware_interface::CallbackReturn::SUCCESS;
+}
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_activate(
+ const rclcpp_lifecycle::State & /*previous_state*/)
+{
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Activating... please wait...");
+
+ // Initialize command positions to current state
+ for (auto & joint : DCJoints_) {
+ joint.joint_command_position = joint.joint_state_position;
+ joint.joint_command_velocity = 0.0;
+ }
+
+ for (size_t i = 0; i < DCJoints_.size(); ++i) {
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"),
+ "Joint %zu command position initialized to: %f", i, DCJoints_[i].joint_command_position);
+ }
+
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Successfully activated!");
+ return hardware_interface::CallbackReturn::SUCCESS;
+}
+
+hardware_interface::CallbackReturn DCHardwareInterface::on_deactivate(
+ const rclcpp_lifecycle::State & /*previous_state*/)
+{
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Deactivating... please wait...");
+
+ for (const auto & joint : DCJoints_) {
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ if (format_maintenance_command(
+ frame, joint.node_id,
+ DecodedCommand{
+ static_cast(MaintenanceCommands::MAINTENANCE_CMD),
+ {static_cast(MaintenanceCommandOptions::MOTOR_STOP_CMD)},
+ {},
+ {}
+ }))
+ {
+ canBus.send(frame);
+ }
+ }
+
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Successfully deactivated!");
+ return hardware_interface::CallbackReturn::SUCCESS;
+}
+
+// =============================================================================
+// CAN Receive Callback
+// =============================================================================
+
+void DCHardwareInterface::on_can_message(const CANLib::CanFrame& frame)
+{
+ can_rx_frame_ = frame;
+
+ if (frame.id != can_response_id) {
+ return;
+ }
+
+ const uint8_t command_nibble = static_cast((frame.data[0] >> 4) & 0x0F);
+ const uint8_t device_id_nibble = static_cast(frame.data[0] & 0x0F);
+
+ for (size_t i = 0; i < DCJoints_.size(); i++) {
+ auto & joint = DCJoints_[i];
+ if (joint.node_id != device_id_nibble) {
+ continue;
+ }
+
+ if (command_nibble == (static_cast(StatusCommands::MOTOR_STATE) >> 4) ||
+ command_nibble == (static_cast(ControlCommands::ABSOLUTE_POS_CONTROL_CMD) >> 4) ||
+ command_nibble == (static_cast(ControlCommands::VELOCITY_CONTROL_CMD) >> 4)) {
+ const double raw_motor_position = static_cast(
+ static_cast((frame.data[2] << 8) | frame.data[1]));
+ const double raw_motor_velocity = static_cast(
+ static_cast((frame.data[4] << 8) | frame.data[3]));
+ const double dir = joint.inverted ? -1.0 : 1.0;
+
+ if (joint.joint_type == joint_type_t::REVOLUTE) {
+ joint.motor_position = dir * calculate_joint_position_from_motor_position(
+ raw_motor_position, joint.gear_ratio);
+ joint.motor_velocity = dir * calculate_joint_angular_velocity_from_motor_velocity(
+ raw_motor_velocity, joint.gear_ratio);
+ } else {
+ joint.motor_position = dir * calculate_joint_displacement_from_motor_position(
+ raw_motor_position, joint.gear_ratio, joint.distance_per_rev);
+ joint.motor_velocity = dir * calculate_joint_linear_velocity_from_motor_velocity(
+ raw_motor_velocity, joint.gear_ratio, joint.distance_per_rev);
+ }
+ return;
+ }
+
+ if (command_nibble == (static_cast(StatusCommands::MOTOR_STATUS) >> 4)) {
+ joint.device_status = frame.data[1];
+ joint.motor_status = static_cast(frame.data[1]);
+ return;
+ }
+
+ if (command_nibble == (static_cast(MaintenanceCommands::MAINTENANCE_CMD) >> 4) && frame.data[0] == 1) {
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "Successfully sent maintenance command");
+ return;
+ }
+
+ if (command_nibble == (static_cast(MaintenanceCommands::DC_SPECS_CMD) >> 4)) {
+ const uint8_t command_id = frame.data[0];
+ const uint8_t motor_type = frame.data[1];
+
+ const uint16_t max_motor_position =
+ static_cast(frame.data[2]) |
+ (static_cast(frame.data[3]) << 8);
+
+ const uint16_t max_motor_velocity =
+ static_cast(frame.data[4]) |
+ (static_cast(frame.data[5]) << 8);
+
+ RCLCPP_INFO(
+ rclcpp::get_logger("DCHardwareInterface"),
+ "DC Config Reply | "
+ "command_id: 0x%02X (%u) | "
+ "motor_type: %u | "
+ "max_motor_position: %u | "
+ "max_motor_velocity: %u",
+ command_id,
+ command_id,
+ motor_type,
+ max_motor_position,
+ max_motor_velocity
+ );
+ return;
+ }
+ }
+}
+
+// =============================================================================
+// Read (update state from CAN data)
+// =============================================================================
+
+hardware_interface::return_type DCHardwareInterface::read(
+ const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/)
+{
+ for (auto & joint : DCJoints_) {
+ joint.joint_state_velocity = joint.motor_velocity;
+ joint.joint_state_position = joint.motor_position;
+
+ // Check device status — allow Undefined(0), Idle(1), Position Control(9), Velocity Control(10)
+ const int s = joint.device_status;
+ if (s != 0 && s != 1 && s != 9 && s != 10) {
+ RCLCPP_ERROR(rclcpp::get_logger("DCHardwareInterface"),
+ "Joint %s in error state: %d", joint.name.c_str(), s);
+ return hardware_interface::return_type::ERROR;
+ }
+
+ // TODO: Telemetry — populate motor_temperature and motor_torque_current via CAN
+ }
+
+ return hardware_interface::return_type::OK;
+}
+
+// =============================================================================
+// Write (send CAN commands)
+// =============================================================================
+
+hardware_interface::return_type DCHardwareInterface::write(
+ const rclcpp::Time & /*time*/, const rclcpp::Duration & period)
+{
+ elapsed_time += period.seconds();
+
+ elapsed_logger_time += period.seconds();
+ if (logger_rate > 0 && elapsed_logger_time > (1.0 / static_cast(logger_rate))) {
+ elapsed_logger_time = 0.0;
+ if (logger_state == 1) {
+ logger_function();
+ }
+ }
+
+ for (size_t i = 0; i < DCJoints_.size(); ++i) {
+ auto & joint = DCJoints_[i];
+ const double curr_status_req = joint.motor_status_req;
+ if (curr_status_req < 0.0 && joint.prev_status_req >= 0.0) {
+ for (auto status_cmd : kStatusCommands) {
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ if (format_status_command(frame, static_cast(status_cmd), joint.node_id)) {
+ canBus.send(frame);
+ }
+ }
+ } else if (curr_status_req > 0.0) {
+ joint.elapsed_status_request_time += period.seconds();
+ if (joint.elapsed_status_request_time > (1.0 / curr_status_req)) {
+ joint.elapsed_status_request_time = 0.0;
+ for (auto status_cmd : kStatusCommands) {
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ if (format_status_command(frame, static_cast(status_cmd), joint.node_id)) {
+ canBus.send(frame);
+ }
+ }
+ }
+ }
+ joint.prev_status_req = curr_status_req;
+ }
+
+ for (size_t i = 0; i < DCJoints_.size(); ++i) {
+ auto & joint = DCJoints_[i];
+ auto doubles_to_payload = [](double high, double low) -> int64_t {
+ return static_cast(
+ (static_cast(high) << 32) | static_cast(low));
+ };
+
+ joint.maintenance_frame = static_cast(doubles_to_payload(
+ joint.maintenance_frame_high, joint.maintenance_frame_low));
+ const auto decoded_maintenance_cmd = unpack_command_full(
+ static_cast(joint.maintenance_data_count),
+ static_cast(joint.maintenance_frame));
+ joint.decoded_maintenance_frame.clear();
+ joint.decoded_maintenance_frame.push_back(decoded_maintenance_cmd.command_id);
+ joint.decoded_maintenance_frame.insert(
+ joint.decoded_maintenance_frame.end(),
+ decoded_maintenance_cmd.u8_data.begin(),
+ decoded_maintenance_cmd.u8_data.end());
+
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ if (!format_maintenance_command(frame, joint.node_id, decoded_maintenance_cmd)) {
+ joint.prev_maintenance_req = joint.motor_maintenance_req;
+ continue;
+ }
+
+ const double curr_maintenance_req = joint.motor_maintenance_req;
+ if (curr_maintenance_req < 0.0 && joint.prev_maintenance_req >= 0.0) {
+ canBus.send(frame);
+ } else if (curr_maintenance_req > 0.0) {
+ joint.elapsed_maintenance_request_time += period.seconds();
+ if (joint.elapsed_maintenance_request_time > (1.0 / curr_maintenance_req)) {
+ joint.elapsed_maintenance_request_time = 0.0;
+ canBus.send(frame);
+ }
+ }
+ joint.prev_maintenance_req = curr_maintenance_req;
+ }
+
+ elapsed_update_time += period.seconds();
+ if (update_rate > 0 && elapsed_update_time > (1.0 / static_cast(update_rate))) {
+ elapsed_update_time = 0.0;
+ if (!DCJoints_.empty()) {
+ const size_t joint_index = static_cast(write_count % num_joints);
+ ++write_count;
+ CANLib::CanFrame frame;
+ frame.id = can_command_id;
+ format_control_command(frame, joint_index);
+ canBus.send(frame);
+ }
+ }
+
+ return hardware_interface::return_type::OK;
+}
+
+// =============================================================================
+// Command Mode Switch
+// =============================================================================
+
+hardware_interface::return_type DCHardwareInterface::perform_command_mode_switch(
+ const std::vector& start_interfaces,
+ const std::vector& stop_interfaces)
+{
+ std::ostringstream ss;
+ ss << "perform_command_mode_switch called. start: [";
+ for (auto &s : start_interfaces) ss << s << ",";
+ ss << "] stop: [";
+ for (auto &s : stop_interfaces) ss << s << ",";
+ ss << "]";
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"), "%s", ss.str().c_str());
+
+ std::vector requested_modes(num_joints, integration_level_t::UNDEFINED);
+
+ // Process stop interfaces
+ for (const auto &ifname : stop_interfaces) {
+ for (int i = 0; i < num_joints; ++i) {
+ if (ifname.find(DCJoints_[static_cast(i)].name) != std::string::npos) {
+ requested_modes[i] = integration_level_t::UNDEFINED;
+ }
+ }
+ }
+
+ // Process start interfaces
+ for (const auto &ifname : start_interfaces) {
+ for (int i = 0; i < num_joints; ++i) {
+ const auto & joint = DCJoints_[static_cast(i)];
+ const std::string pos_if = joint.name + "/" + std::string(hardware_interface::HW_IF_POSITION);
+ const std::string vel_if = joint.name + "/" + std::string(hardware_interface::HW_IF_VELOCITY);
+ if (ifname == pos_if) {
+ requested_modes[i] = integration_level_t::POSITION;
+ } else if (ifname == vel_if) {
+ requested_modes[i] = integration_level_t::VELOCITY;
+ }
+ }
+ }
+
+ // Apply modes
+ for (int i = 0; i < num_joints; ++i) {
+ auto & joint = DCJoints_[static_cast(i)];
+ if (requested_modes[i] == integration_level_t::UNDEFINED) {
+ bool was_stopped = false;
+ for (const auto &ifname : stop_interfaces) {
+ if (ifname.find(joint.name) != std::string::npos) {
+ was_stopped = true;
+ break;
+ }
+ }
+ if (was_stopped) {
+ joint.control_level = integration_level_t::UNDEFINED;
+ joint.joint_command_velocity = 0.0;
+ joint.joint_command_position = joint.joint_state_position;
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"),
+ "Joint %s: stopped -> UNDEFINED", joint.name.c_str());
+ }
+ } else {
+ joint.control_level = requested_modes[i];
+ if (requested_modes[i] == integration_level_t::VELOCITY) {
+ joint.joint_command_velocity = 0.0;
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"),
+ "Joint %s: switched to VELOCITY", joint.name.c_str());
+ } else if (requested_modes[i] == integration_level_t::POSITION) {
+ joint.joint_command_position = joint.joint_state_position;
+ RCLCPP_INFO(rclcpp::get_logger("DCHardwareInterface"),
+ "Joint %s: switched to POSITION (initialized to %f)",
+ joint.name.c_str(), joint.joint_command_position);
+ }
+ }
+ }
+
+ return hardware_interface::return_type::OK;
+}
+
+} // namespace dc_ros2_control
+
+#include "pluginlib/class_list_macros.hpp"
+
+PLUGINLIB_EXPORT_CLASS(
+ dc_ros2_control::DCHardwareInterface, hardware_interface::SystemInterface)
diff --git a/src/hardware_interfaces/killswitch_ros2_control/include/killswitch_ros2_control/killswitch_hardware_interface.hpp b/src/hardware_interfaces/killswitch_ros2_control/include/killswitch_ros2_control/killswitch_hardware_interface.hpp
deleted file mode 100644
index 4636862b..00000000
--- a/src/hardware_interfaces/killswitch_ros2_control/include/killswitch_ros2_control/killswitch_hardware_interface.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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.
-//
-
-#ifndef KILLSWITCH_ROS2_CONTROL__KILLSWITCH_HARDWARE_INTERFACE_HPP_
-#define KILLSWITCH_ROS2_CONTROL__KILLSWITCH_HARDWARE_INTERFACE_HPP_
-
-#include
-#include
-#include
-#include
-#include
-
-#include "hardware_interface/handle.hpp"
-#include "hardware_interface/hardware_info.hpp"
-#include "hardware_interface/system_interface.hpp"
-#include "hardware_interface/types/hardware_interface_return_values.hpp"
-#include "rclcpp/macros.hpp"
-#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
-#include "rclcpp_lifecycle/state.hpp"
-
-#include "umdloop_can_library/SocketCanBus.hpp"
-#include "umdloop_can_library/CanFrame.hpp"
-
-namespace killswitch_ros2_control
-{
-
-/**
- * @brief Hardware interface for CAN-controlled killswitch via ros2_control
- *
- * This interface sends kill commands to the voltage monitoring board via CAN.
- *
- * CAN Protocol (ID: 0x100):
- * - Kill All Power: DATA[0] = 0x01
- * - Kill Main Power: DATA[0] = 0x03
- * - Kill Jetson Power: DATA[0] = 0x05
- *
- * State Interfaces (read by controllers):
- * - kill_all_sent: 1.0 if kill all command was sent
- * - kill_main_sent: 1.0 if kill main command was sent
- * - kill_jetson_sent: 1.0 if kill jetson command was sent
- * - is_connected: Is CAN connected (0.0 or 1.0)
- *
- * Command Interfaces (written by controllers):
- * - kill_all: Set to 1.0 to kill all power
- * - kill_main: Set to 1.0 to kill main power
- * - kill_jetson: Set to 1.0 to kill jetson power
- *
- * Hardware Parameters (from URDF):
- * - can_interface: CAN interface name (default: "can0")
- * - can_id: CAN ID for killswitch commands (default: 0x100)
- */
-class KillswitchHardwareInterface : public hardware_interface::SystemInterface
-{
-public:
- RCLCPP_SHARED_PTR_DEFINITIONS(KillswitchHardwareInterface)
-
- hardware_interface::CallbackReturn on_init(
- const hardware_interface::HardwareInfo & info) override;
-
- hardware_interface::CallbackReturn on_configure(
- const rclcpp_lifecycle::State & previous_state) override;
-
- std::vector export_state_interfaces() override;
-
- std::vector export_command_interfaces() override;
-
- hardware_interface::CallbackReturn on_activate(
- const rclcpp_lifecycle::State & previous_state) override;
-
- hardware_interface::CallbackReturn on_deactivate(
- const rclcpp_lifecycle::State & previous_state) override;
-
- hardware_interface::CallbackReturn on_cleanup(
- const rclcpp_lifecycle::State & previous_state) override;
-
- hardware_interface::CallbackReturn on_shutdown(
- const rclcpp_lifecycle::State & previous_state) override;
-
- hardware_interface::return_type read(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
-
- hardware_interface::return_type write(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
-
-private:
- // CAN message handler
- void onCanMessage(const CANLib::CanFrame& frame);
-
- // Configuration parameters
- std::string can_interface_;
- uint32_t can_id_;
-
- // CAN bus
- CANLib::SocketCanBus canBus_;
- CANLib::CanFrame can_tx_frame_;
- bool can_connected_;
-
- // State variables (track what was sent)
- double kill_all_sent_;
- double kill_main_sent_;
- double kill_jetson_sent_;
- double is_connected_;
-
- // Command variables
- double cmd_kill_all_;
- double cmd_kill_main_;
- double cmd_kill_jetson_;
-
- // CAN command bytes
- static constexpr uint8_t CMD_KILL_ALL = 0x01;
- static constexpr uint8_t CMD_KILL_MAIN = 0x03;
- static constexpr uint8_t CMD_KILL_JETSON = 0x05;
-};
-
-} // namespace killswitch_ros2_control
-
-#endif // KILLSWITCH_ROS2_CONTROL__KILLSWITCH_HARDWARE_INTERFACE_HPP_
diff --git a/src/hardware_interfaces/killswitch_ros2_control/killswitch_hardware_interface.xml b/src/hardware_interfaces/killswitch_ros2_control/killswitch_hardware_interface.xml
deleted file mode 100644
index 91620043..00000000
--- a/src/hardware_interfaces/killswitch_ros2_control/killswitch_hardware_interface.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- Killswitch hardware interface via ros2_control.
- CAN-based power kill commands for main, jetson, and all systems.
-
-
-
diff --git a/src/hardware_interfaces/killswitch_ros2_control/src/killswitch_hardware_interface.cpp b/src/hardware_interfaces/killswitch_ros2_control/src/killswitch_hardware_interface.cpp
deleted file mode 100644
index 3d335e8b..00000000
--- a/src/hardware_interfaces/killswitch_ros2_control/src/killswitch_hardware_interface.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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 "killswitch_ros2_control/killswitch_hardware_interface.hpp"
-
-#include "hardware_interface/types/hardware_interface_type_values.hpp"
-#include "rclcpp/rclcpp.hpp"
-
-namespace killswitch_ros2_control
-{
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_init(
- const hardware_interface::HardwareInfo & info)
-{
- if (hardware_interface::SystemInterface::on_init(info) !=
- hardware_interface::CallbackReturn::SUCCESS)
- {
- return hardware_interface::CallbackReturn::ERROR;
- }
-
- // Initialize state variables
- kill_all_sent_ = 0.0;
- kill_main_sent_ = 0.0;
- kill_jetson_sent_ = 0.0;
- is_connected_ = 0.0;
-
- // Initialize command variables
- cmd_kill_all_ = 0.0;
- cmd_kill_main_ = 0.0;
- cmd_kill_jetson_ = 0.0;
-
- // Parse hardware parameters
- if (info_.hardware_parameters.count("can_interface")) {
- can_interface_ = info_.hardware_parameters.at("can_interface");
- } else {
- can_interface_ = "can0";
- }
-
- if (info_.hardware_parameters.count("can_id")) {
- // Parse hex string (e.g., "0x100") - base 0 auto-detects hex/decimal
- can_id_ = static_cast(std::stoul(info_.hardware_parameters.at("can_id"), nullptr, 0));
- } else {
- can_id_ = 0x100; // Default killswitch CAN ID
- }
-
- can_connected_ = false;
-
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Initialized killswitch on CAN interface %s with ID 0x%X",
- can_interface_.c_str(), can_id_);
-
- return hardware_interface::CallbackReturn::SUCCESS;
-}
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_configure(
- const rclcpp_lifecycle::State & /*previous_state*/)
-{
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Configuring killswitch hardware...");
-
- // Open CAN bus
- if (!canBus_.open(can_interface_,
- std::bind(&KillswitchHardwareInterface::onCanMessage, this, std::placeholders::_1)))
- {
- RCLCPP_WARN(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Failed to open CAN interface %s - running in SIMULATION mode",
- can_interface_.c_str());
- can_connected_ = false;
- } else {
- can_connected_ = true;
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Successfully opened CAN interface %s", can_interface_.c_str());
- }
-
- is_connected_ = can_connected_ ? 1.0 : 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Killswitch hardware configured (%s)", can_connected_ ? "CAN MODE" : "SIMULATION");
-
- return hardware_interface::CallbackReturn::SUCCESS;
-}
-
-void KillswitchHardwareInterface::onCanMessage(const CANLib::CanFrame& frame)
-{
- // Handle incoming CAN messages from voltage monitoring board if needed
- (void)frame; // Currently not expecting responses
-}
-
-std::vector
-KillswitchHardwareInterface::export_state_interfaces()
-{
- std::vector state_interfaces;
-
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "kill_all_sent", &kill_all_sent_));
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "kill_main_sent", &kill_main_sent_));
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "kill_jetson_sent", &kill_jetson_sent_));
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "is_connected", &is_connected_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Exported %zu state interfaces", state_interfaces.size());
-
- return state_interfaces;
-}
-
-std::vector
-KillswitchHardwareInterface::export_command_interfaces()
-{
- std::vector command_interfaces;
-
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- command_interfaces.emplace_back(
- hardware_interface::CommandInterface(name, "kill_all", &cmd_kill_all_));
- command_interfaces.emplace_back(
- hardware_interface::CommandInterface(name, "kill_main", &cmd_kill_main_));
- command_interfaces.emplace_back(
- hardware_interface::CommandInterface(name, "kill_jetson", &cmd_kill_jetson_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Exported %zu command interfaces", command_interfaces.size());
-
- return command_interfaces;
-}
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_activate(
- const rclcpp_lifecycle::State & /*previous_state*/)
-{
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Activating killswitch hardware...");
-
- // Reset commands to safe state
- cmd_kill_all_ = 0.0;
- cmd_kill_main_ = 0.0;
- cmd_kill_jetson_ = 0.0;
-
- return hardware_interface::CallbackReturn::SUCCESS;
-}
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_deactivate(
- const rclcpp_lifecycle::State & /*previous_state*/)
-{
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Deactivating killswitch hardware...");
-
- // Note: We intentionally do NOT send kill commands on deactivate
- // Killswitch should only respond to explicit commands
-
- return hardware_interface::CallbackReturn::SUCCESS;
-}
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_cleanup(
- const rclcpp_lifecycle::State & /*previous_state*/)
-{
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Cleaning up killswitch hardware...");
-
- if (can_connected_) {
- canBus_.close();
- }
-
- can_connected_ = false;
- is_connected_ = 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Killswitch hardware cleanup complete");
-
- return hardware_interface::CallbackReturn::SUCCESS;
-}
-
-hardware_interface::CallbackReturn KillswitchHardwareInterface::on_shutdown(
- const rclcpp_lifecycle::State & previous_state)
-{
- RCLCPP_INFO(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "Shutting down killswitch hardware...");
-
- return on_cleanup(previous_state);
-}
-
-hardware_interface::return_type KillswitchHardwareInterface::read(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
-{
- // CAN messages are handled asynchronously via callback
- return hardware_interface::return_type::OK;
-}
-
-hardware_interface::return_type KillswitchHardwareInterface::write(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
-{
- // Check kill_all command (rising edge detection)
- if (cmd_kill_all_ > 0.5 && kill_all_sent_ < 0.5) {
- if (can_connected_) {
- can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
- can_tx_frame_.dlc = 1;
- can_tx_frame_.data[0] = CMD_KILL_ALL;
- canBus_.send(can_tx_frame_);
- }
-
- kill_all_sent_ = 1.0;
- RCLCPP_ERROR(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "KILL ALL POWER COMMAND SENT%s", can_connected_ ? "" : " (simulated)");
- } else if (cmd_kill_all_ < 0.5) {
- kill_all_sent_ = 0.0; // Reset when command goes low
- }
-
- // Check kill_main command (rising edge detection)
- if (cmd_kill_main_ > 0.5 && kill_main_sent_ < 0.5) {
- if (can_connected_) {
- can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
- can_tx_frame_.dlc = 1;
- can_tx_frame_.data[0] = CMD_KILL_MAIN;
- canBus_.send(can_tx_frame_);
- }
-
- kill_main_sent_ = 1.0;
- RCLCPP_WARN(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "KILL MAIN POWER COMMAND SENT%s", can_connected_ ? "" : " (simulated)");
- } else if (cmd_kill_main_ < 0.5) {
- kill_main_sent_ = 0.0; // Reset when command goes low
- }
-
- // Check kill_jetson command (rising edge detection)
- if (cmd_kill_jetson_ > 0.5 && kill_jetson_sent_ < 0.5) {
- if (can_connected_) {
- can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
- can_tx_frame_.dlc = 1;
- can_tx_frame_.data[0] = CMD_KILL_JETSON;
- canBus_.send(can_tx_frame_);
- }
-
- kill_jetson_sent_ = 1.0;
- RCLCPP_WARN(
- rclcpp::get_logger("KillswitchHardwareInterface"),
- "KILL JETSON POWER COMMAND SENT%s", can_connected_ ? "" : " (simulated)");
- } else if (cmd_kill_jetson_ < 0.5) {
- kill_jetson_sent_ = 0.0; // Reset when command goes low
- }
-
- return hardware_interface::return_type::OK;
-}
-
-} // namespace killswitch_ros2_control
-
-#include "pluginlib/class_list_macros.hpp"
-
-PLUGINLIB_EXPORT_CLASS(
- killswitch_ros2_control::KillswitchHardwareInterface,
- hardware_interface::SystemInterface)
diff --git a/src/hardware_interfaces/laser_ros2_control/include/laser_ros2_control/laser_hardware_interface.hpp b/src/hardware_interfaces/laser_ros2_control/include/laser_ros2_control/laser_hardware_interface.hpp
index 65206ab3..a8e5d56b 100644
--- a/src/hardware_interfaces/laser_ros2_control/include/laser_ros2_control/laser_hardware_interface.hpp
+++ b/src/hardware_interfaces/laser_ros2_control/include/laser_ros2_control/laser_hardware_interface.hpp
@@ -1,26 +1,9 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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.
-//
-
#ifndef LASER_ROS2_CONTROL__LASER_HARDWARE_INTERFACE_HPP_
#define LASER_ROS2_CONTROL__LASER_HARDWARE_INTERFACE_HPP_
#include
#include
#include
-#include
-#include
#include "hardware_interface/handle.hpp"
#include "hardware_interface/hardware_info.hpp"
@@ -29,36 +12,12 @@
#include "rclcpp/macros.hpp"
#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
#include "rclcpp_lifecycle/state.hpp"
-
-#include "umdloop_can_library/SocketCanBus.hpp"
#include "umdloop_can_library/CanFrame.hpp"
+#include "umdloop_can_library/SocketCanBus.hpp"
namespace laser_ros2_control
{
-/**
- * @brief Hardware interface for CAN-controlled spectrometry laser via ros2_control
- *
- * This interface controls a spectrometry laser through CAN bus.
- *
- * CAN Protocol (ID: 0x130):
- * - ON: DATA[0] = 0x60
- * - OFF: DATA[0] = 0x80
- * - Read Temperature: DATA[0] = 0x85
- * - Read Wavelength: DATA[0] = 0x90
- *
- * State Interfaces (read by controllers):
- * - laser_state: 0.0 = OFF, 1.0 = ON
- * - temperature: Laser temperature (if available)
- * - is_connected: Is CAN connected (0.0 or 1.0)
- *
- * Command Interfaces (written by controllers):
- * - laser_command: 0.0 = turn OFF, 1.0 = turn ON
- *
- * Hardware Parameters (from URDF):
- * - can_interface: CAN interface name (default: "can0")
- * - can_id: CAN ID for laser commands (default: 0x130)
- */
class LaserHardwareInterface : public hardware_interface::SystemInterface
{
public:
@@ -66,60 +25,59 @@ class LaserHardwareInterface : public hardware_interface::SystemInterface
hardware_interface::CallbackReturn on_init(
const hardware_interface::HardwareInfo & info) override;
-
hardware_interface::CallbackReturn on_configure(
const rclcpp_lifecycle::State & previous_state) override;
std::vector export_state_interfaces() override;
-
std::vector export_command_interfaces() override;
hardware_interface::CallbackReturn on_activate(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_deactivate(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_cleanup(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_shutdown(
const rclcpp_lifecycle::State & previous_state) override;
hardware_interface::return_type read(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
-
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
hardware_interface::return_type write(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
+
+ void logger_function();
private:
- // CAN message handler
- void onCanMessage(const CANLib::CanFrame& frame);
+ struct LaserJoint
+ {
+ std::string name;
+ uint32_t can_id;
+ double laser_state;
+ double temperature;
+ double is_connected;
+ double status;
+ double laser_command;
+ double status_request;
+ double prev_status_request;
+ double elapsed_status_request_time;
+ };
+
+ void onCanMessage(const CANLib::CanFrame & frame);
- // Configuration parameters
std::string can_interface_;
- uint32_t can_id_;
-
- // CAN bus
CANLib::SocketCanBus canBus_;
CANLib::CanFrame can_tx_frame_;
bool can_connected_;
+ std::vector LASERJoints_;
+ int update_rate_;
+ int logger_rate_;
+ int logger_state_;
+ double elapsed_time_;
+ double elapsed_logger_time_;
- // State variables (hardware → ros2_control)
- double laser_state_; // Current state: 0.0 = OFF, 1.0 = ON
- double temperature_; // Laser temperature
- double is_connected_; // CAN connected status
-
- // Command variables (ros2_control → hardware)
- double laser_command_; // Commanded state
-
- // CAN command bytes
static constexpr uint8_t CMD_LASER_ON = 0x60;
static constexpr uint8_t CMD_LASER_OFF = 0x80;
static constexpr uint8_t CMD_READ_TEMP = 0x85;
- static constexpr uint8_t CMD_READ_WAVELENGTH = 0x90;
};
} // namespace laser_ros2_control
diff --git a/src/hardware_interfaces/laser_ros2_control/src/laser_hardware_interface.cpp b/src/hardware_interfaces/laser_ros2_control/src/laser_hardware_interface.cpp
index 1e74bdea..7e3c528e 100644
--- a/src/hardware_interfaces/laser_ros2_control/src/laser_hardware_interface.cpp
+++ b/src/hardware_interfaces/laser_ros2_control/src/laser_hardware_interface.cpp
@@ -1,273 +1,233 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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 "laser_ros2_control/laser_hardware_interface.hpp"
-#include "hardware_interface/types/hardware_interface_type_values.hpp"
+#include "pluginlib/class_list_macros.hpp"
#include "rclcpp/rclcpp.hpp"
+#include
+
namespace laser_ros2_control
{
+void LaserHardwareInterface::logger_function()
+{
+ if (LASERJoints_.empty()) {
+ return;
+ }
+
+ const auto & joint = LASERJoints_.front();
+ std::ostringstream oss;
+ oss << "\033[2J\033[H \nLASER Logger"
+ << "\n--- HWI Specific ---\n"
+ << "CAN Interface: " << can_interface_
+ << " | HWI Update Rate: " << update_rate_
+ << " | Logger Update Rate: " << logger_rate_ << "\n"
+ << "Elapsed Time since first update: " << elapsed_time_ << "\n"
+ << "\n--- Joint Specific ---\n"
+ << "JOINT: " << joint.name << "\n"
+ << "Parameters: CAN ID: 0x" << std::hex << std::uppercase << joint.can_id << std::dec << "\n"
+ << "-- Commands --\n"
+ << "Laser Command: " << joint.laser_command
+ << " | Status Request: " << joint.status_request << "\n"
+ << "-- State --\n"
+ << "Laser State: " << joint.laser_state
+ << " | Temperature: " << joint.temperature
+ << " | Is Connected: " << joint.is_connected
+ << " | Status: " << joint.status << "\n";
+
+ RCLCPP_INFO(rclcpp::get_logger("LaserHardwareInterface"), "%s", oss.str().c_str());
+}
+
hardware_interface::CallbackReturn LaserHardwareInterface::on_init(
const hardware_interface::HardwareInfo & info)
{
- if (hardware_interface::SystemInterface::on_init(info) !=
- hardware_interface::CallbackReturn::SUCCESS)
+ if (hardware_interface::SystemInterface::on_init(info) !=
+ hardware_interface::CallbackReturn::SUCCESS)
{
return hardware_interface::CallbackReturn::ERROR;
}
- // Initialize state variables
- laser_state_ = 0.0; // OFF
- temperature_ = 0.0;
- is_connected_ = 0.0;
-
- // Initialize command variables
- laser_command_ = 0.0; // OFF
-
- // Parse hardware parameters
- if (info_.hardware_parameters.count("can_interface")) {
- can_interface_ = info_.hardware_parameters.at("can_interface");
- } else {
- can_interface_ = "can0";
- }
-
- if (info_.hardware_parameters.count("can_id")) {
- // Parse hex string (e.g., "0x130") - base 0 auto-detects hex/decimal
- can_id_ = static_cast(std::stoul(info_.hardware_parameters.at("can_id"), nullptr, 0));
- } else {
- can_id_ = 0x130; // Default laser CAN ID
- }
+ can_interface_ = info_.hardware_parameters.count("can_interface") ?
+ info_.hardware_parameters.at("can_interface") : "can0";
+ update_rate_ = info_.hardware_parameters.count("update_rate") ?
+ std::stoi(info_.hardware_parameters.at("update_rate")) : 0;
+ logger_rate_ = info_.hardware_parameters.count("logger_rate") ?
+ std::stoi(info_.hardware_parameters.at("logger_rate")) : 0;
+ logger_state_ = info_.hardware_parameters.count("logger_state") ?
+ std::stoi(info_.hardware_parameters.at("logger_state")) : 0;
+ const uint32_t can_id = info_.hardware_parameters.count("can_id") ?
+ static_cast(std::stoul(info_.hardware_parameters.at("can_id"), nullptr, 0)) : 0x130;
+
+ LASERJoints_.clear();
+ LASERJoints_.push_back(LaserJoint{
+ info_.gpios[0].name,
+ can_id,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ });
can_connected_ = false;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Initialized laser on CAN interface %s with ID 0x%X",
- can_interface_.c_str(), can_id_);
-
+ elapsed_time_ = 0.0;
+ elapsed_logger_time_ = 0.0;
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LaserHardwareInterface::on_configure(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Configuring laser hardware...");
-
- // Open CAN bus
- if (!canBus_.open(can_interface_,
- std::bind(&LaserHardwareInterface::onCanMessage, this, std::placeholders::_1)))
+ if (!canBus_.open(
+ can_interface_,
+ std::bind(&LaserHardwareInterface::onCanMessage, this, std::placeholders::_1)))
{
- RCLCPP_WARN(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Failed to open CAN interface %s - running in SIMULATION mode",
- can_interface_.c_str());
can_connected_ = false;
} else {
can_connected_ = true;
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Successfully opened CAN interface %s", can_interface_.c_str());
}
-
- is_connected_ = can_connected_ ? 1.0 : 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Laser hardware configured (%s)", can_connected_ ? "CAN MODE" : "SIMULATION");
-
+ auto & joint = LASERJoints_.front();
+ joint.is_connected = can_connected_ ? 1.0 : 0.0;
+ joint.status = joint.is_connected;
return hardware_interface::CallbackReturn::SUCCESS;
}
-void LaserHardwareInterface::onCanMessage(const CANLib::CanFrame& frame)
+void LaserHardwareInterface::onCanMessage(const CANLib::CanFrame & frame)
{
- // Handle incoming CAN messages (e.g., temperature readings)
- if (frame.id == can_id_) {
- // Parse response based on command byte
- if (frame.dlc > 0 && frame.data[0] == CMD_READ_TEMP) {
- // Temperature response - parse if available
- if (frame.dlc >= 2) {
- temperature_ = static_cast(frame.data[1]);
- }
- }
+ auto & joint = LASERJoints_.front();
+ if (frame.id == joint.can_id && frame.dlc > 1 && frame.data[0] == CMD_READ_TEMP) {
+ joint.temperature = static_cast(frame.data[1]);
+ joint.status = 1.0;
}
}
-std::vector
-LaserHardwareInterface::export_state_interfaces()
+std::vector LaserHardwareInterface::export_state_interfaces()
{
+ auto & joint = LASERJoints_.front();
std::vector state_interfaces;
-
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- // Laser state (ON/OFF)
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "laser_state", &laser_state_));
-
- // Temperature
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "temperature", &temperature_));
-
- // Connection status
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "is_connected", &is_connected_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Exported %zu state interfaces", state_interfaces.size());
-
+ state_interfaces.emplace_back(joint.name, "laser_state", &joint.laser_state);
+ state_interfaces.emplace_back(joint.name, "temperature", &joint.temperature);
+ state_interfaces.emplace_back(joint.name, "is_connected", &joint.is_connected);
+ state_interfaces.emplace_back(joint.name, "status", &joint.status);
return state_interfaces;
}
-std::vector
-LaserHardwareInterface::export_command_interfaces()
+std::vector LaserHardwareInterface::export_command_interfaces()
{
+ auto & joint = LASERJoints_.front();
std::vector command_interfaces;
-
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- // Laser command (ON/OFF)
- command_interfaces.emplace_back(
- hardware_interface::CommandInterface(name, "laser_command", &laser_command_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Exported %zu command interfaces", command_interfaces.size());
-
+ command_interfaces.emplace_back(joint.name, "laser_command", &joint.laser_command);
+ command_interfaces.emplace_back(joint.name, "status_request", &joint.status_request);
return command_interfaces;
}
hardware_interface::CallbackReturn LaserHardwareInterface::on_activate(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Activating laser hardware...");
-
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LaserHardwareInterface::on_deactivate(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Deactivating laser hardware...");
-
- // Safety: Turn laser OFF on deactivation
+ auto & joint = LASERJoints_.front();
if (can_connected_) {
can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
+ can_tx_frame_.id = joint.can_id;
can_tx_frame_.dlc = 1;
can_tx_frame_.data[0] = CMD_LASER_OFF;
canBus_.send(can_tx_frame_);
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Laser turned OFF (safety)");
}
-
- laser_state_ = 0.0;
-
+ joint.laser_state = 0.0;
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LaserHardwareInterface::on_cleanup(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Cleaning up laser hardware...");
-
- // Ensure laser is OFF before cleanup
+ auto & joint = LASERJoints_.front();
if (can_connected_) {
can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
+ can_tx_frame_.id = joint.can_id;
can_tx_frame_.dlc = 1;
can_tx_frame_.data[0] = CMD_LASER_OFF;
canBus_.send(can_tx_frame_);
-
canBus_.close();
}
-
can_connected_ = false;
- is_connected_ = 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Laser hardware cleanup complete");
-
+ joint.is_connected = 0.0;
+ joint.status = 0.0;
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LaserHardwareInterface::on_shutdown(
const rclcpp_lifecycle::State & previous_state)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Shutting down laser hardware...");
-
return on_cleanup(previous_state);
}
hardware_interface::return_type LaserHardwareInterface::read(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
+ const rclcpp::Time &, const rclcpp::Duration &)
{
- // CAN messages are handled asynchronously via callback
- // State is updated in onCanMessage()
return hardware_interface::return_type::OK;
}
hardware_interface::return_type LaserHardwareInterface::write(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
+ const rclcpp::Time &, const rclcpp::Duration & period)
{
- // Check if laser command changed
- bool commanded_on = (laser_command_ > 0.5);
- bool currently_on = (laser_state_ > 0.5);
+ auto & joint = LASERJoints_.front();
+
+ elapsed_time_ += period.seconds();
+ elapsed_logger_time_ += period.seconds();
+ if (logger_rate_ > 0 && elapsed_logger_time_ > (1.0 / static_cast(logger_rate_))) {
+ elapsed_logger_time_ = 0.0;
+ if (logger_state_ == 1) {
+ logger_function();
+ }
+ }
+ const bool commanded_on = joint.laser_command > 0.5;
+ const bool currently_on = joint.laser_state > 0.5;
if (commanded_on != currently_on) {
if (can_connected_) {
can_tx_frame_ = CANLib::CanFrame();
- can_tx_frame_.id = can_id_;
+ can_tx_frame_.id = joint.can_id;
can_tx_frame_.dlc = 1;
can_tx_frame_.data[0] = commanded_on ? CMD_LASER_ON : CMD_LASER_OFF;
canBus_.send(can_tx_frame_);
}
+ joint.laser_state = commanded_on ? 1.0 : 0.0;
+ }
- laser_state_ = commanded_on ? 1.0 : 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LaserHardwareInterface"),
- "Laser turned %s%s", commanded_on ? "ON" : "OFF",
- can_connected_ ? "" : " (simulated)");
+ if (joint.status_request < 0.0 && joint.prev_status_request >= 0.0) {
+ if (can_connected_) {
+ can_tx_frame_ = CANLib::CanFrame();
+ can_tx_frame_.id = joint.can_id;
+ can_tx_frame_.dlc = 1;
+ can_tx_frame_.data[0] = CMD_READ_TEMP;
+ canBus_.send(can_tx_frame_);
+ }
+ } else if (joint.status_request > 0.0) {
+ joint.elapsed_status_request_time += period.seconds();
+ if (joint.elapsed_status_request_time > (1.0 / joint.status_request)) {
+ joint.elapsed_status_request_time = 0.0;
+ if (can_connected_) {
+ can_tx_frame_ = CANLib::CanFrame();
+ can_tx_frame_.id = joint.can_id;
+ can_tx_frame_.dlc = 1;
+ can_tx_frame_.data[0] = CMD_READ_TEMP;
+ canBus_.send(can_tx_frame_);
+ }
+ }
}
+ joint.prev_status_request = joint.status_request;
return hardware_interface::return_type::OK;
}
} // namespace laser_ros2_control
-#include "pluginlib/class_list_macros.hpp"
-
PLUGINLIB_EXPORT_CLASS(
- laser_ros2_control::LaserHardwareInterface,
- hardware_interface::SystemInterface)
+ laser_ros2_control::LaserHardwareInterface, hardware_interface::SystemInterface)
diff --git a/src/hardware_interfaces/led_ros2_control/CMakeLists.txt b/src/hardware_interfaces/led_ros2_control/CMakeLists.txt
index 01af46cc..82b121bd 100644
--- a/src/hardware_interfaces/led_ros2_control/CMakeLists.txt
+++ b/src/hardware_interfaces/led_ros2_control/CMakeLists.txt
@@ -10,6 +10,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
pluginlib
rclcpp
rclcpp_lifecycle
+ umdloop_can_library
)
# find dependencies
@@ -41,6 +42,10 @@ ament_target_dependencies(
${THIS_PACKAGE_INCLUDE_DEPENDS}
)
+# Link the library target explicitly to ensure its include dirs and
+# compile definitions are available when building this target.
+target_link_libraries(led_ros2_control PUBLIC umdloop_can_library::umdloop_can_library)
+
# Export hardware plugins
pluginlib_export_plugin_description_file(hardware_interface led_hardware_interface.xml)
diff --git a/src/hardware_interfaces/led_ros2_control/include/led_ros2_control/led_hardware_interface.hpp b/src/hardware_interfaces/led_ros2_control/include/led_ros2_control/led_hardware_interface.hpp
index 046e01fb..780f1875 100644
--- a/src/hardware_interfaces/led_ros2_control/include/led_ros2_control/led_hardware_interface.hpp
+++ b/src/hardware_interfaces/led_ros2_control/include/led_ros2_control/led_hardware_interface.hpp
@@ -1,24 +1,12 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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.
-//
-
#ifndef LED_ROS2_CONTROL__LED_HARDWARE_INTERFACE_HPP_
#define LED_ROS2_CONTROL__LED_HARDWARE_INTERFACE_HPP_
#include
#include
#include
+#include
+#include
+#include
#include "hardware_interface/handle.hpp"
#include "hardware_interface/hardware_info.hpp"
@@ -28,33 +16,17 @@
#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
#include "rclcpp_lifecycle/state.hpp"
-namespace led_ros2_control
-{
+#include "umdloop_can_library/SocketCanBus.hpp"
+#include "umdloop_can_library/CanFrame.hpp"
-// GPIO utility functions (Linux sysfs)
-namespace gpio_utils
+namespace CANLib
{
- int setup_gpio_output(int pin);
- void cleanup_gpio(int pin, int fd);
- bool write_gpio(int fd, bool value);
+ struct CanFrame;
}
-/**
- * @brief Hardware interface for LED control via ros2_control
- *
- * This is a SystemInterface for controlling status LEDs through GPIO.
- *
- * State Interfaces (read by controllers):
- * - led_state: Current LED state (0.0 = OFF, 1.0 = ON)
- * - is_connected: Is hardware connected and ready (0.0 or 1.0)
- *
- * Command Interfaces (written by controllers):
- * - led_command: LED command (0.0 = turn OFF, 1.0 = turn ON)
- *
- * Hardware Parameters (from URDF):
- * - gpio_pin: GPIO pin number for LED output (required)
- * - default_state: "on" or "off" (default: "off")
- */
+namespace led_ros2_control
+{
+
class LEDHardwareInterface : public hardware_interface::SystemInterface
{
public:
@@ -62,52 +34,77 @@ class LEDHardwareInterface : public hardware_interface::SystemInterface
hardware_interface::CallbackReturn on_init(
const hardware_interface::HardwareInfo & info) override;
-
hardware_interface::CallbackReturn on_configure(
const rclcpp_lifecycle::State & previous_state) override;
std::vector export_state_interfaces() override;
-
std::vector export_command_interfaces() override;
hardware_interface::CallbackReturn on_activate(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_deactivate(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_cleanup(
const rclcpp_lifecycle::State & previous_state) override;
-
hardware_interface::CallbackReturn on_shutdown(
const rclcpp_lifecycle::State & previous_state) override;
hardware_interface::return_type read(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
-
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
hardware_interface::return_type write(
- const rclcpp::Time & time,
- const rclcpp::Duration & period) override;
-
-private:
- // Configuration parameters
- int gpio_pin_;
- bool default_state_;
-
- // State variables (hardware → ros2_control)
- double led_state_; // Current state: 0.0 = OFF, 1.0 = ON
- double is_connected_; // Hardware ready status
+ const rclcpp::Time & time, const rclcpp::Duration & period) override;
- // Command variables (ros2_control → hardware)
- double led_command_; // Commanded state
+ // -- Helper Functions --
+ void send_command(int can_id, int cmd_id);
+ void logger_function();
- // Hardware interface
- int gpio_fd_;
- bool hw_connected_;
+private:
+ struct LEDGPIO
+ {
+ std::string name;
+ bool is_rgb;
+ uint32_t node_id;
+ double status;
+ double intensity;
+ double red_command;
+ double green_command;
+ double blue_command;
+ double status_request;
+ double prev_status_request;
+ double elapsed_status_request_time;
+ double prev_intensity;
+ double prev_red_command;
+ double prev_green_command;
+ double prev_blue_command;
+
+ std::vector state_interface_names;
+ std::vector command_interface_names;
+ std::unordered_map parameters;
+ };
+
+ std::string can_interface_;
+ CANLib::SocketCanBus canBus;
+ CANLib::CanFrame can_tx_frame_;
+
+ // CAN bus ID (constant for this HWI)
+ uint32_t can_id_ = 0;
+
+ std::vector LEDGPIOs_;
+ int update_rate_;
+ int logger_rate_;
+ int logger_state_;
+ double elapsed_time_;
+ double elapsed_logger_time_;
+
+ // Multiplexor byte
+ static constexpr uint8_t CMD_INTENSITY = 0x20;
+ static constexpr uint8_t CMD_RGB = 0x30;
+
+ // Parameter to confirm whether to send command or not via CAN
+ static constexpr uint8_t DECLINE_SEND = 0;
+ static constexpr uint8_t CONFIRM_SEND = 1;
};
} // namespace led_ros2_control
#endif // LED_ROS2_CONTROL__LED_HARDWARE_INTERFACE_HPP_
-
diff --git a/src/hardware_interfaces/led_ros2_control/package.xml b/src/hardware_interfaces/led_ros2_control/package.xml
index 10258b2e..f6920a4f 100644
--- a/src/hardware_interfaces/led_ros2_control/package.xml
+++ b/src/hardware_interfaces/led_ros2_control/package.xml
@@ -15,6 +15,10 @@
rclcpp
rclcpp_lifecycle
+
+ umdloop_can_library
+ umdloop_can_library
+
ament_lint_auto
ament_lint_common
diff --git a/src/hardware_interfaces/led_ros2_control/src/led_hardware_interface.cpp b/src/hardware_interfaces/led_ros2_control/src/led_hardware_interface.cpp
index 2b2c0b4c..59741686 100644
--- a/src/hardware_interfaces/led_ros2_control/src/led_hardware_interface.cpp
+++ b/src/hardware_interfaces/led_ros2_control/src/led_hardware_interface.cpp
@@ -1,305 +1,310 @@
-// Copyright (c) 2024 UMD Loop
-//
-// 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 "led_ros2_control/led_hardware_interface.hpp"
#include
#include
#include
-#include "hardware_interface/types/hardware_interface_type_values.hpp"
+#include "pluginlib/class_list_macros.hpp"
#include "rclcpp/rclcpp.hpp"
+#include
+#include
+#include
namespace led_ros2_control
{
-// GPIO utility functions implementation
-namespace gpio_utils
-{
-
-int setup_gpio_output(int pin)
-{
- // Export GPIO
- int fd = ::open("/sys/class/gpio/export", O_WRONLY);
- if (fd >= 0) {
- std::string pin_str = std::to_string(pin);
- ::write(fd, pin_str.c_str(), pin_str.length());
- ::close(fd);
- usleep(100000); // Wait for GPIO to be exported
- }
-
- // Set direction to output
- std::stringstream direction_path;
- direction_path << "/sys/class/gpio/gpio" << pin << "/direction";
- fd = ::open(direction_path.str().c_str(), O_WRONLY);
- if (fd < 0) {
- return -1;
- }
- ::write(fd, "out", 3);
- ::close(fd);
+void LEDHardwareInterface::send_command(int can_id, int cmd_id){
+ CANLib::CanFrame frame;
+ frame.id = can_id;
+ frame.dlc = 2;
+ frame.data.fill(0);
+ frame.data[0] = cmd_id;
+ frame.data[1] = CONFIRM_SEND;
- // Open value file
- std::stringstream value_path;
- value_path << "/sys/class/gpio/gpio" << pin << "/value";
- int value_fd = ::open(value_path.str().c_str(), O_RDWR);
-
- return value_fd;
+ canBus.send(frame);
}
-void cleanup_gpio(int pin, int fd)
+void LEDHardwareInterface::logger_function()
{
- if (fd >= 0) {
- ::close(fd);
+ if (LEDGPIOs_.empty()) {
+ return;
}
- // Unexport GPIO
- int export_fd = ::open("/sys/class/gpio/unexport", O_WRONLY);
- if (export_fd >= 0) {
- std::string pin_str = std::to_string(pin);
- ::write(export_fd, pin_str.c_str(), pin_str.length());
- ::close(export_fd);
+ for (const auto & gpio : LEDGPIOs_) {
+ std::ostringstream oss;
+ oss << "\033[2J\033[H \nLED Logger"
+ << "\n--- HWI Specific ---\n"
+ << "CAN Interface: " << can_interface_
+ << " | CAN ID: 0x" << std::hex << std::uppercase << can_id_ << std::dec
+ << " | HWI Update Rate: " << update_rate_
+ << " | Logger Update Rate: " << logger_rate_ << "\n"
+ << "Elapsed Time since first update: " << elapsed_time_ << "\n"
+ << "\n--- GPIO Specific ---\n"
+ << "GPIO: " << gpio.name << " | Node ID: 0x" << std::hex << std::uppercase << gpio.node_id << std::dec << "\n"
+ << "-- Commands --\n"
+ << "Red Command: " << gpio.red_command
+ << " | Green Command: " << gpio.green_command
+ << " | Blue Command: " << gpio.blue_command
+ << " | Status Request: " << gpio.status_request << "\n"
+ << "-- State --\n"
+ << "Status: " << gpio.status << "\n";
+
+ RCLCPP_INFO(rclcpp::get_logger("LEDHardwareInterface"), "%s", oss.str().c_str());
}
}
-bool write_gpio(int fd, bool value)
-{
- if (fd < 0) return false;
-
- char val = value ? '1' : '0';
- lseek(fd, 0, SEEK_SET);
- return (::write(fd, &val, 1) == 1);
-}
-
-} // namespace gpio_utils
-
-// Hardware Interface Implementation
-
hardware_interface::CallbackReturn LEDHardwareInterface::on_init(
const hardware_interface::HardwareInfo & info)
{
- if (hardware_interface::SystemInterface::on_init(info) !=
- hardware_interface::CallbackReturn::SUCCESS)
+ if (hardware_interface::SystemInterface::on_init(info) !=
+ hardware_interface::CallbackReturn::SUCCESS)
{
return hardware_interface::CallbackReturn::ERROR;
}
-
- // Parse hardware parameters
- if (!info_.hardware_parameters.count("gpio_pin")) {
- RCLCPP_ERROR(
- rclcpp::get_logger("LEDHardwareInterface"),
- "gpio_pin parameter is required");
- return hardware_interface::CallbackReturn::ERROR;
- }
- gpio_pin_ = std::stoi(info_.hardware_parameters.at("gpio_pin"));
-
- // Default state
- if (info_.hardware_parameters.count("default_state")) {
- default_state_ = (info_.hardware_parameters.at("default_state") == "on");
+
+ // General HWI parameters
+ can_interface_ = info_.hardware_parameters.count("can_interface") ?
+ info_.hardware_parameters.at("can_interface") : "can0";
+ update_rate_ = info_.hardware_parameters.count("update_rate") ?
+ std::stoi(info_.hardware_parameters.at("update_rate")) : 5;
+ logger_rate_ = info_.hardware_parameters.count("logger_rate") ?
+ std::stoi(info_.hardware_parameters.at("logger_rate")) : 0;
+ logger_state_ = info_.hardware_parameters.count("logger_state") ?
+ std::stoi(info_.hardware_parameters.at("logger_state")) : 0;
+ if (info_.hardware_parameters.count("can_id")) {
+ try {
+ can_id_ = static_cast(std::stoul(info_.hardware_parameters.at("can_id"), nullptr, 0));
+ } catch (const std::exception & e) {
+ RCLCPP_ERROR(rclcpp::get_logger("LEDHardwareInterface"), "Failed to parse 'can_id': %s", e.what());
+ return hardware_interface::CallbackReturn::ERROR;
+ }
} else {
- default_state_ = false; // OFF by default
+ RCLCPP_ERROR(rclcpp::get_logger("LEDHardwareInterface"), "CAN ID parameter 'can_id' is missing in hardware configuration.");
+ return hardware_interface::CallbackReturn::ERROR;
}
- // Initialize state variables
- led_state_ = default_state_ ? 1.0 : 0.0;
- is_connected_ = 0.0;
+ for (auto& gpio : info_.gpios) {
+ // Collect state interface names
+ std::vector state_if_names;
+ for (const auto &si : gpio.state_interfaces) {
+ state_if_names.push_back(si.name);
+ }
- // Initialize command variables
- led_command_ = default_state_ ? 1.0 : 0.0;
+ // Collect command interface names
+ std::vector command_if_names;
+ for (const auto &ci : gpio.command_interfaces) {
+ command_if_names.push_back(ci.name);
+ }
- gpio_fd_ = -1;
- hw_connected_ = false;
+ // Copy parameters into an unordered_map
+ std::unordered_map params_map;
+ for (const auto &p : gpio.parameters) {
+ params_map.emplace(p.first, p.second);
+ }
+ // Determine whether this gpio is rgb from its parameters
+ bool is_rgb_flag = false;
+ if (params_map.count("is_rgb")) {
+ const std::string &v = params_map.at("is_rgb");
+ is_rgb_flag = (v == "true" || v == "1");
+ }
+ // parse per-gpio node_id if provided
+ uint32_t gpio_node_id = 0;
+ if (params_map.count("node_id")) {
+ try {
+ gpio_node_id = static_cast(std::stoul(params_map.at("node_id")));
+ } catch (const std::exception & e) {
+ RCLCPP_WARN(rclcpp::get_logger("LEDHardwareInterface"), "Failed to parse gpio node_id for '%s': %s", gpio.name.c_str(), e.what());
+ }
+ }
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Initialized LED on GPIO pin %d (default: %s)",
- gpio_pin_, default_state_ ? "ON" : "OFF");
+ LEDGPIOs_.push_back(
+ LEDGPIO{
+ .name = gpio.name,
+ .is_rgb = is_rgb_flag,
+ .node_id = gpio_node_id,
+ .status = 0.0,
+ .intensity = 0.0,
+ .red_command = 0.0,
+ .green_command = 0.0,
+ .blue_command = 0.0,
+ .status_request = 0.0,
+ .prev_status_request = 0.0,
+ .elapsed_status_request_time = 0.0,
+ .prev_intensity = std::numeric_limits::quiet_NaN(),
+ .prev_red_command = std::numeric_limits::quiet_NaN(),
+ .prev_green_command = std::numeric_limits::quiet_NaN(),
+ .prev_blue_command = std::numeric_limits::quiet_NaN(),
+
+ .state_interface_names = state_if_names,
+ .command_interface_names = command_if_names,
+ .parameters = params_map
+ }
+ );
+ }
+ elapsed_time_ = 0.0;
+ elapsed_logger_time_ = 0.0;
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LEDHardwareInterface::on_configure(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Configuring LED hardware...");
-
- // Setup GPIO output
- gpio_fd_ = gpio_utils::setup_gpio_output(gpio_pin_);
- if (gpio_fd_ < 0) {
- RCLCPP_WARN(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Failed to setup GPIO output on pin %d - running in SIMULATION mode", gpio_pin_);
- hw_connected_ = false; // Mark as simulation mode
- } else {
- hw_connected_ = true;
- // Set initial state on real hardware
- gpio_utils::write_gpio(gpio_fd_, default_state_);
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Successfully configured LED hardware (GPIO mode)");
- }
-
- is_connected_ = hw_connected_ ? 1.0 : 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "LED hardware configured (%s)", hw_connected_ ? "REAL GPIO" : "SIMULATION");
-
return hardware_interface::CallbackReturn::SUCCESS;
}
-std::vector
-LEDHardwareInterface::export_state_interfaces()
+std::vector LEDHardwareInterface::export_state_interfaces()
{
std::vector state_interfaces;
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- // LED state
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "led_state", &led_state_));
-
- // Connection status
- state_interfaces.emplace_back(
- hardware_interface::StateInterface(name, "is_connected", &is_connected_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Exported %zu state interfaces", state_interfaces.size());
-
+ for (auto & gpio : LEDGPIOs_) {
+ for (auto & iface : gpio.state_interface_names) {
+ double * val = nullptr;
+ if (iface == "status") {
+ val = &gpio.status;
+ } else {
+ RCLCPP_WARN(
+ rclcpp::get_logger("LEDHardwareInterface"),
+ "Unknown state interface '%s' for GPIO '%s'",
+ iface.c_str(), gpio.name.c_str());
+ continue;
+ }
+ state_interfaces.emplace_back(hardware_interface::StateInterface(
+ gpio.name, iface, val));
+ }
+ }
return state_interfaces;
}
-std::vector
-LEDHardwareInterface::export_command_interfaces()
+std::vector LEDHardwareInterface::export_command_interfaces()
{
std::vector command_interfaces;
- // Use the gpio name from URDF
- const std::string& name = info_.gpios[0].name;
-
- // LED command
- command_interfaces.emplace_back(
- hardware_interface::CommandInterface(name, "led_command", &led_command_));
-
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Exported %zu command interfaces", command_interfaces.size());
+ for (auto & gpio : LEDGPIOs_) {
+ for (auto & iface : gpio.command_interface_names) {
+ double * val = nullptr;
+ // Only export RGB interfaces when is_rgb is true; otherwise export intensity
+ if (gpio.is_rgb) {
+ if (iface == "red") {
+ val = &gpio.red_command;
+ } else if (iface == "green") {
+ val = &gpio.green_command;
+ } else if (iface == "blue") {
+ val = &gpio.blue_command;
+ } else if (iface == "status_request") {
+ val = &gpio.status_request;
+ } else {
+ // skip intensity when rgb mode
+ continue;
+ }
+ } else {
+ if (iface == "intensity") {
+ val = &gpio.intensity;
+ } else if (iface == "status_request") {
+ val = &gpio.status_request;
+ } else {
+ // skip rgb channels when not rgb
+ continue;
+ }
+ }
+ command_interfaces.emplace_back(hardware_interface::CommandInterface(
+ gpio.name, iface, val));
+ }
+ }
return command_interfaces;
}
hardware_interface::CallbackReturn LEDHardwareInterface::on_activate(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Activating LED hardware...");
-
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LEDHardwareInterface::on_deactivate(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Deactivating LED hardware...");
-
- // Turn LED off on deactivation (safety)
- if (hw_connected_ && gpio_fd_ >= 0) {
- gpio_utils::write_gpio(gpio_fd_, false);
- led_state_ = 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "LED turned OFF (deactivation)");
- }
-
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LEDHardwareInterface::on_cleanup(
- const rclcpp_lifecycle::State & /*previous_state*/)
+ const rclcpp_lifecycle::State &)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Cleaning up LED hardware...");
-
- // Ensure LED is OFF before cleanup
- if (gpio_fd_ >= 0) {
- gpio_utils::write_gpio(gpio_fd_, false);
- }
-
- // Cleanup GPIO - unexport and close file descriptor
- if (gpio_fd_ >= 0) {
- gpio_utils::cleanup_gpio(gpio_pin_, gpio_fd_);
- gpio_fd_ = -1;
+ for (auto & gpio : LEDGPIOs_) {
+ gpio.status = 0.0;
}
-
- hw_connected_ = false;
- is_connected_ = 0.0;
-
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "LED hardware cleanup complete");
-
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn LEDHardwareInterface::on_shutdown(
const rclcpp_lifecycle::State & previous_state)
{
- RCLCPP_INFO(
- rclcpp::get_logger("LEDHardwareInterface"),
- "Shutting down LED hardware...");
-
- // Delegate to cleanup to release resources
return on_cleanup(previous_state);
}
hardware_interface::return_type LEDHardwareInterface::read(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
+ const rclcpp::Time &, const rclcpp::Duration &)
{
- // LED state is known from commands, no need to read from GPIO
return hardware_interface::return_type::OK;
}
hardware_interface::return_type LEDHardwareInterface::write(
- const rclcpp::Time & /*time*/,
- const rclcpp::Duration & /*period*/)
+ const rclcpp::Time &, const rclcpp::Duration & period)
{
- // Check if LED command changed
- bool commanded_on = (led_command_ > 0.5);
- bool currently_on = (led_state_ > 0.5);
-
- if (commanded_on != currently_on) {
- // Write to real GPIO if available, otherwise simulate
- if (hw_connected_ && gpio_fd_ >= 0) {
- gpio_utils::write_gpio(gpio_fd_, commanded_on);
+ elapsed_time_ += period.seconds();
+ elapsed_logger_time_ += period.seconds();
+ if (logger_rate_ > 0 && elapsed_logger_time_ > (1.0 / static_cast(logger_rate_))) {
+ elapsed_logger_time_ = 0.0;
+ if (logger_state_ == 1) {
+ logger_function();
+ }
+ }
+
+ for (auto & gpio : LEDGPIOs_) {
+ if (gpio.status_request < 0.0 && gpio.prev_status_request >= 0.0) {
+ } else if (gpio.status_request > 0.0) {
+ gpio.elapsed_status_request_time += period.seconds();
+ if (gpio.elapsed_status_request_time > (1.0 / gpio.status_request)) {
+ gpio.elapsed_status_request_time = 0.0;
+ }
+ }
+ gpio.prev_status_request = gpio.status_request;
+ // Build and send CAN frames for LED commands
+ CANLib::CanFrame frame;
+ frame.id = can_id_;
+ frame.dlc = 8;
+ frame.data.fill(0);
+
+ if (gpio.is_rgb) {
+ // RGB mode: send when any channel changed
+ const double r = gpio.red_command;
+ const double g = gpio.green_command;
+ const double b = gpio.blue_command;
+ const bool changed = (std::isnan(gpio.prev_red_command) || r != gpio.prev_red_command) ||
+ (std::isnan(gpio.prev_green_command) || g != gpio.prev_green_command) ||
+ (std::isnan(gpio.prev_blue_command) || b != gpio.prev_blue_command);
+ if (changed) {
+ frame.data[0] = static_cast(CMD_RGB + static_cast(gpio.node_id & 0xFF));
+ frame.data[1] = std::clamp(static_cast(std::round(r)), 0, 255);
+ frame.data[2] = std::clamp(static_cast(std::round(g)), 0, 255);
+ frame.data[3] = std::clamp(static_cast(std::round(b)), 0, 255);
+ canBus.send(frame);
+ gpio.prev_red_command = r;
+ gpio.prev_green_command = g;
+ gpio.prev_blue_command = b;
+ RCLCPP_INFO(rclcpp::get_logger("LEDHardwareInterface"), "Sent RGB frame to node 0x%X (CAN ID 0x%X)", gpio.node_id, can_id_);
+ }
+ } else {
+ const double intensity = gpio.intensity;
+ const bool changed = std::isnan(gpio.prev_intensity) || intensity != gpio.prev_intensity;
+ if (changed) {
+ frame.data[0] = static_cast(CMD_INTENSITY + static_cast(gpio.node_id & 0xFF));
+ frame.data[1] = std::clamp(static_cast(std::round(intensity)), 0, 255);
+ canBus.send(frame);
+ gpio.prev_intensity = intensity;
+ RCLCPP_INFO(rclcpp::get_logger("LEDHardwareInterface"), "Sent intensity frame to node 0x%X (CAN ID 0x%X val=%d)", gpio.node_id, can_id_, frame.data[1]);
+ }
}
-
- led_state_ = commanded_on ? 1.0 : 0.0;
-
- RCLCPP_DEBUG(
- rclcpp::get_logger("LEDHardwareInterface"),
- "LED turned %s%s", commanded_on ? "ON" : "OFF",
- hw_connected_ ? "" : " (simulated)");
}
return hardware_interface::return_type::OK;
@@ -307,9 +312,5 @@ hardware_interface::return_type LEDHardwareInterface::write(
} // namespace led_ros2_control
-#include "pluginlib/class_list_macros.hpp"
-
PLUGINLIB_EXPORT_CLASS(
- led_ros2_control::LEDHardwareInterface,
- hardware_interface::SystemInterface)
-
+ led_ros2_control::LEDHardwareInterface, hardware_interface::SystemInterface)
diff --git a/src/hardware_interfaces/limit_switch_ros2_control/CMakeLists.txt b/src/hardware_interfaces/limit_switch_ros2_control/CMakeLists.txt
new file mode 100644
index 00000000..fa7913cd
--- /dev/null
+++ b/src/hardware_interfaces/limit_switch_ros2_control/CMakeLists.txt
@@ -0,0 +1,64 @@
+cmake_minimum_required(VERSION 3.8)
+project(limit_switch_ros2_control)
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+set(THIS_PACKAGE_INCLUDE_DEPENDS
+ hardware_interface
+ pluginlib
+ rclcpp
+ rclcpp_lifecycle
+ umdloop_can_library
+)
+
+find_package(ament_cmake REQUIRED)
+find_package(backward_ros REQUIRED)
+find_package(ament_cmake_auto REQUIRED)
+foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
+ find_package(${Dependency} REQUIRED)
+endforeach()
+
+ament_auto_find_build_dependencies()
+
+add_library(
+ limit_switch_ros2_control
+ SHARED
+ src/limit_switch_hardware_interface.cpp
+)
+
+target_compile_features(limit_switch_ros2_control PUBLIC cxx_std_20)
+target_include_directories(limit_switch_ros2_control PUBLIC
+ $
+ $
+)
+
+ament_target_dependencies(limit_switch_ros2_control PUBLIC ${THIS_PACKAGE_INCLUDE_DEPENDS})
+target_link_libraries(limit_switch_ros2_control PUBLIC umdloop_can_library::umdloop_can_library)
+
+pluginlib_export_plugin_description_file(hardware_interface limit_switch_hardware_interface.xml)
+
+install(
+ DIRECTORY include/
+ DESTINATION include/limit_switch_ros2_control
+)
+
+install(TARGETS limit_switch_ros2_control
+ EXPORT export_limit_switch_ros2_control
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ RUNTIME DESTINATION bin
+)
+
+if(BUILD_TESTING)
+ find_package(ament_lint_auto REQUIRED)
+ set(ament_cmake_copyright_FOUND TRUE)
+ set(ament_cmake_cpplint_FOUND TRUE)
+ ament_lint_auto_find_test_dependencies()
+endif()
+
+ament_export_targets(export_limit_switch_ros2_control)
+ament_export_include_directories(include)
+ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS})
+ament_package()
diff --git a/src/hardware_interfaces/limit_switch_ros2_control/include/limit_switch_ros2_control/limit_switch_hardware_interface.hpp b/src/hardware_interfaces/limit_switch_ros2_control/include/limit_switch_ros2_control/limit_switch_hardware_interface.hpp
new file mode 100644
index 00000000..5610c8cd
--- /dev/null
+++ b/src/hardware_interfaces/limit_switch_ros2_control/include/limit_switch_ros2_control/limit_switch_hardware_interface.hpp
@@ -0,0 +1,95 @@
+#ifndef LIMIT_SWITCH_ROS2_CONTROL__LIMIT_SWITCH_HARDWARE_INTERFACE_HPP_
+#define LIMIT_SWITCH_ROS2_CONTROL__LIMIT_SWITCH_HARDWARE_INTERFACE_HPP_
+
+#include
+#include
+#include