Skip to content

Commit 949aa8c

Browse files
mahaarbodestogl
andauthored
Added GPIO parsing and test (#436)
* Added support for GPIO tag in URDF. Co-authored-by: Denis Štogl <[email protected]>
1 parent 6328200 commit 949aa8c

File tree

4 files changed

+311
-4
lines changed

4 files changed

+311
-4
lines changed

hardware_interface/include/hardware_interface/hardware_info.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ struct InterfaceInfo
3131
{
3232
/**
3333
* Name of the command interfaces that can be set, e.g. "position", "velocity", etc.
34-
* Used by joints.
34+
* Used by joints and GPIOs.
3535
*/
3636
std::string name;
3737
/// (Optional) Minimal allowed values of the interface.
3838
std::string min;
3939
/// (Optional) Maximal allowed values of the interface.
4040
std::string max;
41+
/// (Optional) The datatype of the interface, e.g. "bool", "int". Used by GPIOs.
42+
std::string data_type;
43+
/// (Optional) If the handle is an array, the size of the array. Used by GPIOs.
44+
int size;
4145
};
4246

4347
/**
@@ -48,16 +52,16 @@ struct ComponentInfo
4852
{
4953
/// Name of the component.
5054
std::string name;
51-
/// Type of the component: sensor or joint.
55+
/// Type of the component: sensor, joint, or GPIO.
5256
std::string type;
5357
/**
5458
* Name of the command interfaces that can be set, e.g. "position", "velocity", etc.
55-
* Used by joints.
59+
* Used by joints and GPIOs.
5660
*/
5761
std::vector<InterfaceInfo> command_interfaces;
5862
/**
5963
* Name of the state interfaces that can be read, e.g. "position", "velocity", etc.
60-
* Used by Joints and Sensors.
64+
* Used by joints, sensors and GPIOs.
6165
*/
6266
std::vector<InterfaceInfo> state_interfaces;
6367
/// (Optional) Key-value pairs of component parameters, e.g. min/max values or serial number.
@@ -84,6 +88,11 @@ struct HardwareInfo
8488
* Required for Sensor and optional for System Hardware.
8589
*/
8690
std::vector<ComponentInfo> sensors;
91+
/**
92+
* Map of GPIO provided by the hardware where the key is a descriptive name of the GPIO.
93+
* Optional for any hardware components.
94+
*/
95+
std::vector<ComponentInfo> gpios;
8796
/**
8897
* Map of transmissions to calcualte ration between joints and physical actuators.
8998
* Optional for Actuator and System Hardware.

hardware_interface/src/component_parser.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <string>
1818
#include <unordered_map>
1919
#include <vector>
20+
#include <regex>
2021

2122
#include "hardware_interface/hardware_info.hpp"
2223
#include "hardware_interface/component_parser.hpp"
@@ -30,11 +31,14 @@ constexpr const auto kClassTypeTag = "plugin";
3031
constexpr const auto kParamTag = "param";
3132
constexpr const auto kJointTag = "joint";
3233
constexpr const auto kSensorTag = "sensor";
34+
constexpr const auto kGPIOTag = "gpio";
3335
constexpr const auto kTransmissionTag = "transmission";
3436
constexpr const auto kCommandInterfaceTag = "command_interface";
3537
constexpr const auto kStateInterfaceTag = "state_interface";
3638
constexpr const auto kMinTag = "min";
3739
constexpr const auto kMaxTag = "max";
40+
constexpr const auto kDataTypeAttribute = "data_type";
41+
constexpr const auto kSizeAttribute = "size";
3842
constexpr const auto kNameAttribute = "name";
3943
constexpr const auto kTypeAttribute = "type";
4044
} // namespace
@@ -103,6 +107,61 @@ std::string get_attribute_value(
103107
return get_attribute_value(element_it, attribute_name, std::string(tag_name));
104108
}
105109

110+
/// Parse optional size attribute
111+
/**
112+
* Parses an XMLElement and returns the value of the size attribute.
113+
* If not specified, defaults to 1. If not given a positive integer, throws an error.
114+
*
115+
* \param[in] elem XMLElement that has the size attribute.
116+
* \return The size.
117+
* \throws std::runtime_error if not given a positive non-zero integer as value.
118+
*/
119+
std::size_t parse_size_attribute(
120+
const tinyxml2::XMLElement * elem)
121+
{
122+
const tinyxml2::XMLAttribute * attr = elem->FindAttribute(kSizeAttribute);
123+
124+
if (!attr) {
125+
return 1;
126+
}
127+
128+
std::size_t size;
129+
// Regex used to check for non-zero positive int
130+
std::string s = attr->Value();
131+
std::regex int_re("[1-9][0-9]*");
132+
if (std::regex_match(s, int_re)) {
133+
size = std::stoi(s);
134+
} else {
135+
throw std::runtime_error(
136+
"Could not parse size tag in \"" + std::string(elem->Name()) + "\"." +
137+
"Got \"" + s + "\", but expected a non-zero positive integer.");
138+
}
139+
140+
return size;
141+
}
142+
143+
/// Parse data_type attribute
144+
/**
145+
* Parses an XMLElement and returns the value of the data_type attribute.
146+
* Defaults to "double" if not specified.
147+
*
148+
* \param[in] elem XMLElement that has the data_type attribute.
149+
* \return string specifying the data type.
150+
*/
151+
std::string parse_data_type_attribute(
152+
const tinyxml2::XMLElement * elem)
153+
{
154+
const tinyxml2::XMLAttribute * attr = elem->FindAttribute(kDataTypeAttribute);
155+
std::string data_type;
156+
if (!attr) {
157+
data_type = "double";
158+
} else {
159+
data_type = attr->Value();
160+
}
161+
162+
return data_type;
163+
}
164+
106165
/// Search XML snippet from URDF for parameters.
107166
/**
108167
* \param[in] params_it pointer to the iterator where parameters info should be found
@@ -159,6 +218,10 @@ hardware_interface::InterfaceInfo parse_interfaces_from_xml(
159218
interface.max = interface_param->second;
160219
}
161220

221+
// Default to a single double
222+
interface.data_type = "double";
223+
interface.size = 1;
224+
162225
return interface;
163226
}
164227

@@ -200,6 +263,52 @@ ComponentInfo parse_component_from_xml(const tinyxml2::XMLElement * component_it
200263
return component;
201264
}
202265

266+
/// Search XML snippet from URDF for information about a complex component.
267+
/**
268+
* A complex component can have a non-double data type specified on its interfaces,
269+
* and the interface may be an array of a fixed size of the data type.
270+
*
271+
* \param[in] component_it pointer to the iterator where component
272+
* info should befound
273+
* \throws std::runtime_error if a required component attribute or tag is not found.
274+
*/
275+
ComponentInfo parse_complex_component_from_xml(
276+
const tinyxml2::XMLElement * component_it)
277+
{
278+
ComponentInfo component;
279+
280+
// Find name, type and class of a component
281+
component.type = component_it->Name();
282+
component.name = get_attribute_value(component_it, kNameAttribute, component.type);
283+
284+
// Parse all command interfaces
285+
const auto * command_interfaces_it = component_it->FirstChildElement(kCommandInterfaceTag);
286+
while (command_interfaces_it) {
287+
component.command_interfaces.push_back(parse_interfaces_from_xml(command_interfaces_it));
288+
component.command_interfaces.back().data_type =
289+
parse_data_type_attribute(command_interfaces_it);
290+
component.command_interfaces.back().size = parse_size_attribute(command_interfaces_it);
291+
command_interfaces_it = command_interfaces_it->NextSiblingElement(kCommandInterfaceTag);
292+
}
293+
294+
// Parse state interfaces
295+
const auto * state_interfaces_it = component_it->FirstChildElement(kStateInterfaceTag);
296+
while (state_interfaces_it) {
297+
component.state_interfaces.push_back(parse_interfaces_from_xml(state_interfaces_it));
298+
component.state_interfaces.back().data_type = parse_data_type_attribute(state_interfaces_it);
299+
component.state_interfaces.back().size = parse_size_attribute(state_interfaces_it);
300+
state_interfaces_it = state_interfaces_it->NextSiblingElement(kStateInterfaceTag);
301+
}
302+
303+
// Parse parameters
304+
const auto * params_it = component_it->FirstChildElement(kParamTag);
305+
if (params_it) {
306+
component.parameters = parse_parameters_from_xml(params_it);
307+
}
308+
309+
return component;
310+
}
311+
203312
/// Parse a control resource from an "ros2_control" tag.
204313
/**
205314
* \param[in] ros2_control_it pointer to ros2_control element
@@ -229,6 +338,9 @@ HardwareInfo parse_resource_from_xml(const tinyxml2::XMLElement * ros2_control_i
229338
hardware.joints.push_back(parse_component_from_xml(ros2_control_child_it) );
230339
} else if (!std::string(kSensorTag).compare(ros2_control_child_it->Name())) {
231340
hardware.sensors.push_back(parse_component_from_xml(ros2_control_child_it) );
341+
} else if (!std::string(kGPIOTag).compare(ros2_control_child_it->Name())) {
342+
hardware.gpios.push_back(
343+
parse_complex_component_from_xml(ros2_control_child_it));
232344
} else if (!std::string(kTransmissionTag).compare(ros2_control_child_it->Name())) {
233345
hardware.transmissions.push_back(parse_component_from_xml(ros2_control_child_it) );
234346
} else {

hardware_interface/test/test_component_parser.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,109 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_only)
497497
ASSERT_THAT(hardware_info.transmissions[0].parameters, SizeIs(1));
498498
EXPECT_EQ(hardware_info.transmissions[0].parameters.at("joint_to_actuator"), "${1024/PI}");
499499
}
500+
501+
TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_robot_with_gpio)
502+
{
503+
std::string urdf_to_test =
504+
std::string(ros2_control_test_assets::urdf_head) +
505+
ros2_control_test_assets::valid_urdf_ros2_control_system_robot_with_gpio +
506+
ros2_control_test_assets::urdf_tail;
507+
const auto control_hardware = parse_control_resources_from_urdf(urdf_to_test);
508+
ASSERT_THAT(control_hardware, SizeIs(1));
509+
auto hardware_info = control_hardware.front();
510+
511+
EXPECT_EQ(hardware_info.name, "RRBotSystemWithGPIO");
512+
EXPECT_EQ(hardware_info.type, "system");
513+
EXPECT_EQ(
514+
hardware_info.hardware_class_type,
515+
"ros2_control_demo_hardware/RRBotSystemWithGPIOHardware");
516+
517+
ASSERT_THAT(hardware_info.joints, SizeIs(2));
518+
519+
EXPECT_EQ(hardware_info.joints[0].name, "joint1");
520+
EXPECT_EQ(hardware_info.joints[0].type, "joint");
521+
522+
EXPECT_EQ(hardware_info.joints[1].name, "joint2");
523+
EXPECT_EQ(hardware_info.joints[1].type, "joint");
524+
525+
ASSERT_THAT(hardware_info.gpios, SizeIs(2));
526+
527+
EXPECT_EQ(hardware_info.gpios[0].name, "flange_analog_IOs");
528+
EXPECT_EQ(hardware_info.gpios[0].type, "gpio");
529+
EXPECT_THAT(hardware_info.gpios[0].state_interfaces, SizeIs(3));
530+
EXPECT_THAT(hardware_info.gpios[0].command_interfaces, SizeIs(1));
531+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[0].name, "analog_output1");
532+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[1].name, "analog_input1");
533+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[2].name, "analog_input2");
534+
535+
EXPECT_EQ(hardware_info.gpios[1].name, "flange_vacuum");
536+
EXPECT_EQ(hardware_info.gpios[1].type, "gpio");
537+
EXPECT_THAT(hardware_info.gpios[1].state_interfaces, SizeIs(1));
538+
EXPECT_THAT(hardware_info.gpios[1].command_interfaces, SizeIs(1));
539+
EXPECT_EQ(hardware_info.gpios[1].state_interfaces[0].name, "vacuum");
540+
EXPECT_EQ(hardware_info.gpios[1].command_interfaces[0].name, "vacuum");
541+
}
542+
543+
TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_with_size_and_data_type)
544+
{
545+
std::string urdf_to_test =
546+
std::string(ros2_control_test_assets::urdf_head) +
547+
ros2_control_test_assets::valid_urdf_ros2_control_system_robot_with_size_and_data_type +
548+
ros2_control_test_assets::urdf_tail;
549+
const auto control_hardware = parse_control_resources_from_urdf(urdf_to_test);
550+
ASSERT_THAT(control_hardware, SizeIs(1));
551+
auto hardware_info = control_hardware.front();
552+
553+
EXPECT_EQ(hardware_info.name, "RRBotSystemWithSizeAndDataType");
554+
EXPECT_EQ(hardware_info.type, "system");
555+
EXPECT_EQ(
556+
hardware_info.hardware_class_type,
557+
"ros2_control_demo_hardware/RRBotSystemWithSizeAndDataType");
558+
559+
ASSERT_THAT(hardware_info.joints, SizeIs(1));
560+
561+
EXPECT_EQ(hardware_info.joints[0].name, "joint1");
562+
EXPECT_EQ(hardware_info.joints[0].type, "joint");
563+
EXPECT_THAT(hardware_info.joints[0].command_interfaces, SizeIs(1));
564+
EXPECT_EQ(hardware_info.joints[0].command_interfaces[0].name, HW_IF_POSITION);
565+
EXPECT_EQ(hardware_info.joints[0].command_interfaces[0].data_type, "double");
566+
EXPECT_EQ(hardware_info.joints[0].command_interfaces[0].size, 1);
567+
EXPECT_THAT(hardware_info.joints[0].state_interfaces, SizeIs(1));
568+
EXPECT_EQ(hardware_info.joints[0].state_interfaces[0].name, HW_IF_POSITION);
569+
EXPECT_EQ(hardware_info.joints[0].state_interfaces[0].data_type, "double");
570+
EXPECT_EQ(hardware_info.joints[0].state_interfaces[0].size, 1);
571+
572+
ASSERT_THAT(hardware_info.gpios, SizeIs(1));
573+
574+
EXPECT_EQ(hardware_info.gpios[0].name, "flange_IOS");
575+
EXPECT_EQ(hardware_info.gpios[0].type, "gpio");
576+
EXPECT_THAT(hardware_info.gpios[0].command_interfaces, SizeIs(1));
577+
EXPECT_EQ(hardware_info.gpios[0].command_interfaces[0].name, "digital_output");
578+
EXPECT_EQ(hardware_info.gpios[0].command_interfaces[0].data_type, "bool");
579+
EXPECT_EQ(hardware_info.gpios[0].command_interfaces[0].size, 2);
580+
EXPECT_THAT(hardware_info.gpios[0].state_interfaces, SizeIs(2));
581+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[0].name, "analog_input");
582+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[0].data_type, "double");
583+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[0].size, 3);
584+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[1].name, "image");
585+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[1].data_type, "cv::Mat");
586+
EXPECT_EQ(hardware_info.gpios[0].state_interfaces[1].size, 1);
587+
}
588+
589+
TEST_F(TestComponentParser, negative_size_throws_error)
590+
{
591+
std::string urdf_to_test =
592+
std::string(ros2_control_test_assets::urdf_head) +
593+
ros2_control_test_assets::invalid_urdf2_ros2_control_illegal_size +
594+
ros2_control_test_assets::urdf_tail;
595+
ASSERT_THROW(parse_control_resources_from_urdf(urdf_to_test), std::runtime_error);
596+
}
597+
598+
TEST_F(TestComponentParser, noninteger_size_throws_error)
599+
{
600+
std::string urdf_to_test =
601+
std::string(ros2_control_test_assets::urdf_head) +
602+
ros2_control_test_assets::invalid_urdf2_ros2_control_illegal_size2 +
603+
ros2_control_test_assets::urdf_tail;
604+
ASSERT_THROW(parse_control_resources_from_urdf(urdf_to_test), std::runtime_error);
605+
}

0 commit comments

Comments
 (0)