Skip to content

Commit d40377d

Browse files
Let sensors also export state interfaces of joints (#1885)
1 parent 5e53146 commit d40377d

File tree

7 files changed

+172
-30
lines changed

7 files changed

+172
-30
lines changed

hardware_interface/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
2121

2222
find_package(ament_cmake REQUIRED)
2323
find_package(ament_cmake_gen_version_h REQUIRED)
24+
find_package(backward_ros REQUIRED)
2425
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
2526
find_package(${Dependency} REQUIRED)
2627
endforeach()

hardware_interface/include/hardware_interface/sensor_interface.hpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class SensorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
123123
virtual CallbackReturn on_init(const HardwareInfo & hardware_info)
124124
{
125125
info_ = hardware_info;
126+
parse_state_interface_descriptions(info_.joints, joint_state_interfaces_);
126127
parse_state_interface_descriptions(info_.sensors, sensor_state_interfaces_);
127128
return CallbackReturn::SUCCESS;
128129
};
@@ -179,7 +180,8 @@ class SensorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
179180

180181
std::vector<StateInterface::ConstSharedPtr> state_interfaces;
181182
state_interfaces.reserve(
182-
unlisted_interface_descriptions.size() + sensor_state_interfaces_.size());
183+
unlisted_interface_descriptions.size() + sensor_state_interfaces_.size() +
184+
joint_state_interfaces_.size());
183185

184186
// add InterfaceDescriptions and create StateInterfaces from the descriptions and add to maps.
185187
for (const auto & description : unlisted_interface_descriptions)
@@ -201,6 +203,14 @@ class SensorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
201203
state_interfaces.push_back(std::const_pointer_cast<const StateInterface>(state_interface));
202204
}
203205

206+
for (const auto & [name, descr] : joint_state_interfaces_)
207+
{
208+
auto state_interface = std::make_shared<StateInterface>(descr);
209+
sensor_states_map_.insert(std::make_pair(name, state_interface));
210+
joint_states_.push_back(state_interface);
211+
state_interfaces.push_back(std::const_pointer_cast<const StateInterface>(state_interface));
212+
}
213+
204214
return state_interfaces;
205215
}
206216

@@ -274,10 +284,12 @@ class SensorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
274284
protected:
275285
HardwareInfo info_;
276286
// interface names to InterfaceDescription
287+
std::unordered_map<std::string, InterfaceDescription> joint_state_interfaces_;
277288
std::unordered_map<std::string, InterfaceDescription> sensor_state_interfaces_;
278289
std::unordered_map<std::string, InterfaceDescription> unlisted_state_interfaces_;
279290

280291
// Exported Command- and StateInterfaces in order they are listed in the hardware description.
292+
std::vector<StateInterface::SharedPtr> joint_states_;
281293
std::vector<StateInterface::SharedPtr> sensor_states_;
282294
std::vector<StateInterface::SharedPtr> unlisted_states_;
283295

hardware_interface/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<buildtool_depend>ament_cmake</buildtool_depend>
1111
<buildtool_depend>ament_cmake_gen_version_h</buildtool_depend>
1212

13+
<depend>backward_ros</depend>
1314
<depend>control_msgs</depend>
1415
<depend>lifecycle_msgs</depend>
1516
<depend>pluginlib</depend>

hardware_interface/src/resource_manager.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -615,11 +615,10 @@ class ResourceStorage
615615
auto interfaces = hardware.export_state_interfaces();
616616
const auto interface_names = add_state_interfaces(interfaces);
617617

618-
RCLCPP_WARN(
619-
get_logger(),
618+
RCLCPP_WARN_EXPRESSION(
619+
get_logger(), interface_names.empty(),
620620
"Importing state interfaces for the hardware '%s' returned no state interfaces.",
621621
hardware.get_name().c_str());
622-
623622
hardware_info_map_[hardware.get_name()].state_interfaces = interface_names;
624623
available_state_interfaces_.reserve(
625624
available_state_interfaces_.capacity() + interface_names.size());

hardware_interface/test/test_component_interfaces.cpp

Lines changed: 129 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ class DummySensor : public hardware_interface::SensorInterface
271271
// We can read some voltage level
272272
std::vector<hardware_interface::StateInterface> state_interfaces;
273273
state_interfaces.emplace_back(
274-
hardware_interface::StateInterface("joint1", "voltage", &voltage_level_));
274+
hardware_interface::StateInterface("sens1", "voltage", &voltage_level_));
275275

276276
return state_interfaces;
277277
}
@@ -332,15 +332,72 @@ class DummySensorDefault : public hardware_interface::SensorInterface
332332

333333
CallbackReturn on_configure(const rclcpp_lifecycle::State & /*previous_state*/) override
334334
{
335-
for (const auto & [name, descr] : sensor_state_interfaces_)
335+
set_state("sens1/voltage", 0.0);
336+
read_calls_ = 0;
337+
return CallbackReturn::SUCCESS;
338+
}
339+
340+
std::string get_name() const override { return "DummySensorDefault"; }
341+
342+
hardware_interface::return_type read(
343+
const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) override
344+
{
345+
++read_calls_;
346+
if (read_calls_ == TRIGGER_READ_WRITE_ERROR_CALLS)
347+
{
348+
return hardware_interface::return_type::ERROR;
349+
}
350+
351+
// no-op, static value
352+
set_state("sens1/voltage", voltage_level_hw_value_);
353+
return hardware_interface::return_type::OK;
354+
}
355+
356+
CallbackReturn on_error(const rclcpp_lifecycle::State & /*previous_state*/) override
357+
{
358+
if (!recoverable_error_happened_)
359+
{
360+
recoverable_error_happened_ = true;
361+
return CallbackReturn::SUCCESS;
362+
}
363+
else
364+
{
365+
return CallbackReturn::ERROR;
366+
}
367+
return CallbackReturn::FAILURE;
368+
}
369+
370+
private:
371+
double voltage_level_hw_value_ = 0x666;
372+
373+
// Helper variables to initiate error on read
374+
int read_calls_ = 0;
375+
bool recoverable_error_happened_ = false;
376+
};
377+
378+
class DummySensorJointDefault : public hardware_interface::SensorInterface
379+
{
380+
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override
381+
{
382+
if (
383+
hardware_interface::SensorInterface::on_init(info) !=
384+
hardware_interface::CallbackReturn::SUCCESS)
336385
{
337-
set_state(name, 0.0);
386+
return hardware_interface::CallbackReturn::ERROR;
338387
}
388+
389+
return CallbackReturn::SUCCESS;
390+
}
391+
392+
CallbackReturn on_configure(const rclcpp_lifecycle::State & /*previous_state*/) override
393+
{
394+
set_state("joint1/position", 10.0);
395+
set_state("sens1/voltage", 0.0);
339396
read_calls_ = 0;
340397
return CallbackReturn::SUCCESS;
341398
}
342399

343-
std::string get_name() const override { return "DummySensorDefault"; }
400+
std::string get_name() const override { return "DummySensorJointDefault"; }
344401

345402
hardware_interface::return_type read(
346403
const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) override
@@ -352,7 +409,8 @@ class DummySensorDefault : public hardware_interface::SensorInterface
352409
}
353410

354411
// no-op, static value
355-
set_state("joint1/voltage", voltage_level_hw_value_);
412+
set_state("joint1/position", position_hw_value_);
413+
set_state("sens1/voltage", voltage_level_hw_value_);
356414
return hardware_interface::return_type::OK;
357415
}
358416

@@ -371,6 +429,7 @@ class DummySensorDefault : public hardware_interface::SensorInterface
371429
}
372430

373431
private:
432+
double position_hw_value_ = 0x777;
374433
double voltage_level_hw_value_ = 0x666;
375434

376435
// Helper variables to initiate error on read
@@ -907,9 +966,9 @@ TEST(TestComponentInterfaces, dummy_sensor)
907966

908967
auto state_interfaces = sensor_hw.export_state_interfaces();
909968
ASSERT_EQ(1u, state_interfaces.size());
910-
EXPECT_EQ("joint1/voltage", state_interfaces[0]->get_name());
969+
EXPECT_EQ("sens1/voltage", state_interfaces[0]->get_name());
911970
EXPECT_EQ("voltage", state_interfaces[0]->get_interface_name());
912-
EXPECT_EQ("joint1", state_interfaces[0]->get_prefix_name());
971+
EXPECT_EQ("sens1", state_interfaces[0]->get_prefix_name());
913972
EXPECT_TRUE(std::isnan(state_interfaces[0]->get_value()));
914973

915974
// Not updated because is is UNCONFIGURED
@@ -948,29 +1007,83 @@ TEST(TestComponentInterfaces, dummy_sensor_default)
9481007
auto state_interfaces = sensor_hw.export_state_interfaces();
9491008
ASSERT_EQ(1u, state_interfaces.size());
9501009
{
951-
auto [contains, position] =
952-
test_components::vector_contains(state_interfaces, "joint1/voltage");
1010+
auto [contains, position] = test_components::vector_contains(state_interfaces, "sens1/voltage");
9531011
EXPECT_TRUE(contains);
954-
EXPECT_EQ("joint1/voltage", state_interfaces[position]->get_name());
1012+
EXPECT_EQ("sens1/voltage", state_interfaces[position]->get_name());
9551013
EXPECT_EQ("voltage", state_interfaces[position]->get_interface_name());
956-
EXPECT_EQ("joint1", state_interfaces[position]->get_prefix_name());
1014+
EXPECT_EQ("sens1", state_interfaces[position]->get_prefix_name());
9571015
EXPECT_TRUE(std::isnan(state_interfaces[position]->get_value()));
9581016
}
9591017

9601018
// Not updated because is is UNCONFIGURED
961-
auto si_joint1_vol = test_components::vector_contains(state_interfaces, "joint1/voltage").second;
1019+
auto si_sens1_vol = test_components::vector_contains(state_interfaces, "sens1/voltage").second;
9621020
sensor_hw.read(TIME, PERIOD);
963-
EXPECT_TRUE(std::isnan(state_interfaces[si_joint1_vol]->get_value()));
1021+
EXPECT_TRUE(std::isnan(state_interfaces[si_sens1_vol]->get_value()));
9641022

9651023
// Updated because is is INACTIVE
9661024
state = sensor_hw.configure();
9671025
EXPECT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, state.id());
9681026
EXPECT_EQ(hardware_interface::lifecycle_state_names::INACTIVE, state.label());
969-
EXPECT_EQ(0.0, state_interfaces[si_joint1_vol]->get_value());
1027+
EXPECT_EQ(0.0, state_interfaces[si_sens1_vol]->get_value());
1028+
1029+
// It can read now
1030+
sensor_hw.read(TIME, PERIOD);
1031+
EXPECT_EQ(0x666, state_interfaces[si_sens1_vol]->get_value());
1032+
}
1033+
1034+
TEST(TestComponentInterfaces, dummy_sensor_default_joint)
1035+
{
1036+
hardware_interface::Sensor sensor_hw(
1037+
std::make_unique<test_components::DummySensorJointDefault>());
1038+
1039+
const std::string urdf_to_test =
1040+
std::string(ros2_control_test_assets::urdf_head) +
1041+
ros2_control_test_assets::valid_urdf_ros2_control_joint_voltage_sensor +
1042+
ros2_control_test_assets::urdf_tail;
1043+
const std::vector<hardware_interface::HardwareInfo> control_resources =
1044+
hardware_interface::parse_control_resources_from_urdf(urdf_to_test);
1045+
const hardware_interface::HardwareInfo sensor_res = control_resources[0];
1046+
rclcpp::Node::SharedPtr node = std::make_shared<rclcpp::Node>("test_system_components");
1047+
auto state =
1048+
sensor_hw.initialize(sensor_res, node->get_logger(), node->get_node_clock_interface());
1049+
ASSERT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, state.id());
1050+
ASSERT_EQ(hardware_interface::lifecycle_state_names::UNCONFIGURED, state.label());
1051+
1052+
auto state_interfaces = sensor_hw.export_state_interfaces();
1053+
ASSERT_EQ(2u, state_interfaces.size());
1054+
1055+
auto [contains_sens1_vol, si_sens1_vol] =
1056+
test_components::vector_contains(state_interfaces, "sens1/voltage");
1057+
ASSERT_TRUE(contains_sens1_vol);
1058+
EXPECT_EQ("sens1/voltage", state_interfaces[si_sens1_vol]->get_name());
1059+
EXPECT_EQ("voltage", state_interfaces[si_sens1_vol]->get_interface_name());
1060+
EXPECT_EQ("sens1", state_interfaces[si_sens1_vol]->get_prefix_name());
1061+
EXPECT_TRUE(std::isnan(state_interfaces[si_sens1_vol]->get_value()));
1062+
1063+
auto [contains_joint1_pos, si_joint1_pos] =
1064+
test_components::vector_contains(state_interfaces, "joint1/position");
1065+
ASSERT_TRUE(contains_joint1_pos);
1066+
EXPECT_EQ("joint1/position", state_interfaces[si_joint1_pos]->get_name());
1067+
EXPECT_EQ("position", state_interfaces[si_joint1_pos]->get_interface_name());
1068+
EXPECT_EQ("joint1", state_interfaces[si_joint1_pos]->get_prefix_name());
1069+
EXPECT_TRUE(std::isnan(state_interfaces[si_joint1_pos]->get_value()));
1070+
1071+
// Not updated because is is UNCONFIGURED
1072+
sensor_hw.read(TIME, PERIOD);
1073+
EXPECT_TRUE(std::isnan(state_interfaces[si_sens1_vol]->get_value()));
1074+
EXPECT_TRUE(std::isnan(state_interfaces[si_joint1_pos]->get_value()));
1075+
1076+
// Updated because is is INACTIVE
1077+
state = sensor_hw.configure();
1078+
ASSERT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, state.id());
1079+
ASSERT_EQ(hardware_interface::lifecycle_state_names::INACTIVE, state.label());
1080+
EXPECT_EQ(0.0, state_interfaces[si_sens1_vol]->get_value());
1081+
EXPECT_EQ(10.0, state_interfaces[si_joint1_pos]->get_value());
9701082

9711083
// It can read now
9721084
sensor_hw.read(TIME, PERIOD);
973-
EXPECT_EQ(0x666, state_interfaces[si_joint1_vol]->get_value());
1085+
EXPECT_EQ(0x666, state_interfaces[si_sens1_vol]->get_value());
1086+
EXPECT_EQ(0x777, state_interfaces[si_joint1_pos]->get_value());
9741087
}
9751088

9761089
// BEGIN (Handle export change): for backward compatibility
@@ -1708,7 +1821,7 @@ TEST(TestComponentInterfaces, dummy_sensor_default_read_error_behavior)
17081821
EXPECT_EQ(hardware_interface::lifecycle_state_names::UNCONFIGURED, state.label());
17091822

17101823
// activate again and expect reset values
1711-
auto si_joint1_vol = test_components::vector_contains(state_interfaces, "joint1/voltage").second;
1824+
auto si_joint1_vol = test_components::vector_contains(state_interfaces, "sens1/voltage").second;
17121825
state = sensor_hw.configure();
17131826
EXPECT_EQ(state_interfaces[si_joint1_vol]->get_value(), 0.0);
17141827

hardware_interface/test/test_component_interfaces_custom_export.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -245,21 +245,20 @@ TEST(TestComponentInterfaces, dummy_sensor_default_custom_export)
245245
auto state_interfaces = sensor_hw.export_state_interfaces();
246246
ASSERT_EQ(2u, state_interfaces.size());
247247
{
248-
auto [contains, position] =
249-
test_components::vector_contains(state_interfaces, "joint1/voltage");
250-
EXPECT_TRUE(contains);
251-
EXPECT_EQ("joint1/voltage", state_interfaces[position]->get_name());
248+
auto [contains, position] = test_components::vector_contains(state_interfaces, "sens1/voltage");
249+
ASSERT_TRUE(contains);
250+
EXPECT_EQ("sens1/voltage", state_interfaces[position]->get_name());
252251
EXPECT_EQ("voltage", state_interfaces[position]->get_interface_name());
253-
EXPECT_EQ("joint1", state_interfaces[position]->get_prefix_name());
252+
EXPECT_EQ("sens1", state_interfaces[position]->get_prefix_name());
254253
EXPECT_TRUE(std::isnan(state_interfaces[position]->get_value()));
255254
}
256255
{
257256
auto [contains, position] =
258-
test_components::vector_contains(state_interfaces, "joint1/some_unlisted_interface");
259-
EXPECT_TRUE(contains);
260-
EXPECT_EQ("joint1/some_unlisted_interface", state_interfaces[position]->get_name());
257+
test_components::vector_contains(state_interfaces, "sens1/some_unlisted_interface");
258+
ASSERT_TRUE(contains);
259+
EXPECT_EQ("sens1/some_unlisted_interface", state_interfaces[position]->get_name());
261260
EXPECT_EQ("some_unlisted_interface", state_interfaces[position]->get_interface_name());
262-
EXPECT_EQ("joint1", state_interfaces[position]->get_prefix_name());
261+
EXPECT_EQ("sens1", state_interfaces[position]->get_prefix_name());
263262
}
264263
}
265264

ros2_control_test_assets/include/ros2_control_test_assets/components_urdfs.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,24 @@ const auto valid_urdf_ros2_control_voltage_sensor_only =
636636
<plugin>ros2_control_demo_hardware/SingleJointVoltageSensor</plugin>
637637
<param name="example_param_read_for_sec">2</param>
638638
</hardware>
639-
<sensor name="joint1">
639+
<sensor name="sens1">
640+
<state_interface name="voltage" initial_value="0.0"/>
641+
</sensor>
642+
</ros2_control>
643+
)";
644+
645+
// Joint+Voltage Sensor
646+
const auto valid_urdf_ros2_control_joint_voltage_sensor =
647+
R"(
648+
<ros2_control name="SingleJointVoltage" type="sensor">
649+
<hardware>
650+
<plugin>ros2_control_demo_hardware/SingleJointVoltageSensor</plugin>
651+
<param name="example_param_read_for_sec">2</param>
652+
</hardware>
653+
<joint name="joint1">
654+
<state_interface name="position"/>
655+
</joint>
656+
<sensor name="sens1">
640657
<state_interface name="voltage" initial_value="0.0"/>
641658
</sensor>
642659
</ros2_control>

0 commit comments

Comments
 (0)