Skip to content

Commit a206d01

Browse files
Add magnetic_field_sensor semantic component (#2627) (#2655)
(cherry picked from commit df846fe) Co-authored-by: Aarav Gupta <[email protected]>
1 parent f534713 commit a206d01

File tree

6 files changed

+230
-0
lines changed

6 files changed

+230
-0
lines changed

controller_interface/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ if(BUILD_TESTING)
7979
${sensor_msgs_TARGETS}
8080
)
8181

82+
ament_add_gmock(test_magnetic_field_sensor test/test_magnetic_field_sensor.cpp)
83+
target_link_libraries(test_magnetic_field_sensor
84+
controller_interface
85+
hardware_interface::hardware_interface
86+
${sensor_msgs_TARGETS}
87+
)
88+
8289
ament_add_gmock(test_pose_sensor test/test_pose_sensor.cpp)
8390
target_link_libraries(test_pose_sensor
8491
controller_interface
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 Aarav Gupta
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef SEMANTIC_COMPONENTS__MAGNETIC_FIELD_SENSOR_HPP_
16+
#define SEMANTIC_COMPONENTS__MAGNETIC_FIELD_SENSOR_HPP_
17+
18+
#include <string>
19+
20+
#include "semantic_components/semantic_component_interface.hpp"
21+
#include "sensor_msgs/msg/magnetic_field.hpp"
22+
23+
namespace semantic_components
24+
{
25+
class MagneticFieldSensor : public SemanticComponentInterface<sensor_msgs::msg::MagneticField>
26+
{
27+
public:
28+
explicit MagneticFieldSensor(const std::string & name)
29+
: SemanticComponentInterface(
30+
name, {name + "/" + "magnetic_field.x", name + "/" + "magnetic_field.y",
31+
name + "/" + "magnetic_field.z"})
32+
{
33+
}
34+
35+
/// Returns values as sensor_msgs::msg::MagneticField
36+
/**
37+
* \return MagneticField message from values
38+
*/
39+
bool get_values_as_message(sensor_msgs::msg::MagneticField & message)
40+
{
41+
update_data_from_interfaces();
42+
message.magnetic_field.x = data_[0];
43+
message.magnetic_field.y = data_[1];
44+
message.magnetic_field.z = data_[2];
45+
return true;
46+
}
47+
48+
private:
49+
/**
50+
* @brief Update the data array from the state interfaces.
51+
* @note This method is thread-safe and non-blocking.
52+
* @note This method might return stale data if the data is not updated. This is to ensure that
53+
* the data from the sensor is not discontinuous.
54+
*/
55+
void update_data_from_interfaces()
56+
{
57+
for (auto i = 0u; i < data_.size(); ++i)
58+
{
59+
const auto data = state_interfaces_[i].get().get_optional();
60+
if (data.has_value())
61+
{
62+
data_[i] = data.value();
63+
}
64+
}
65+
}
66+
67+
// Array to store the data of the magnetic field sensor
68+
std::array<double, 3> data_{{0.0, 0.0, 0.0}};
69+
};
70+
71+
} // namespace semantic_components
72+
73+
#endif // SEMANTIC_COMPONENTS__MAGNETIC_FIELD_SENSOR_HPP_
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 Aarav Gupta
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "test_magnetic_field_sensor.hpp"
16+
17+
#include <memory>
18+
#include <string>
19+
#include <vector>
20+
#include "sensor_msgs/msg/magnetic_field.hpp"
21+
22+
void MagneticFieldSensorTest::TearDown() { magnetic_field_sensor_.reset(nullptr); }
23+
24+
TEST_F(MagneticFieldSensorTest, validate_all)
25+
{
26+
// Create the magnetic field sensor
27+
magnetic_field_sensor_ = std::make_unique<TestableMagneticFieldSensor>(sensor_name_);
28+
29+
// validate the component name
30+
ASSERT_EQ(magnetic_field_sensor_->name_, sensor_name_);
31+
32+
// Validate the space reserved for interface_names_ and state_interfaces_
33+
// Note : Using capacity() for state_interfaces_ as no such interfaces are defined yet
34+
ASSERT_EQ(magnetic_field_sensor_->interface_names_.size(), size_);
35+
ASSERT_EQ(magnetic_field_sensor_->state_interfaces_.capacity(), size_);
36+
37+
// Validate the default interface_names_
38+
ASSERT_TRUE(
39+
std::equal(
40+
magnetic_field_sensor_->interface_names_.begin(),
41+
magnetic_field_sensor_->interface_names_.end(), full_interface_names_.begin(),
42+
full_interface_names_.end()));
43+
44+
// Get the interface names
45+
std::vector<std::string> interface_names = magnetic_field_sensor_->get_state_interface_names();
46+
47+
// Assign values
48+
auto magnetic_field_x = std::make_shared<hardware_interface::StateInterface>(
49+
sensor_name_, magnetic_field_interface_names_[0], &magnetic_field_values_[0]);
50+
auto magnetic_field_y = std::make_shared<hardware_interface::StateInterface>(
51+
sensor_name_, magnetic_field_interface_names_[1], &magnetic_field_values_[1]);
52+
auto magnetic_field_z = std::make_shared<hardware_interface::StateInterface>(
53+
sensor_name_, magnetic_field_interface_names_[2], &magnetic_field_values_[2]);
54+
55+
// Create local state interface vector
56+
std::vector<hardware_interface::LoanedStateInterface> temp_state_interfaces;
57+
temp_state_interfaces.reserve(10);
58+
59+
// Insert the interfaces in jumbled sequence
60+
temp_state_interfaces.emplace_back(magnetic_field_y);
61+
temp_state_interfaces.emplace_back(magnetic_field_z);
62+
temp_state_interfaces.emplace_back(magnetic_field_x);
63+
64+
// Now call the function to make them in order like interface_names
65+
magnetic_field_sensor_->assign_loaned_state_interfaces(temp_state_interfaces);
66+
67+
// Validate the count of state_interfaces_
68+
ASSERT_EQ(magnetic_field_sensor_->state_interfaces_.size(), size_);
69+
70+
// Validate get_values_as_message()
71+
sensor_msgs::msg::MagneticField temp_message;
72+
ASSERT_TRUE(magnetic_field_sensor_->get_values_as_message(temp_message));
73+
ASSERT_EQ(temp_message.magnetic_field.x, magnetic_field_values_[0]);
74+
ASSERT_EQ(temp_message.magnetic_field.y, magnetic_field_values_[1]);
75+
ASSERT_EQ(temp_message.magnetic_field.z, magnetic_field_values_[2]);
76+
77+
// release the state_interfaces_
78+
magnetic_field_sensor_->release_interfaces();
79+
80+
// validate the count of state_interfaces_
81+
ASSERT_EQ(magnetic_field_sensor_->state_interfaces_.size(), 0u);
82+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025 Aarav Gupta
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef TEST_MAGNETIC_FIELD_SENSOR_HPP_
16+
#define TEST_MAGNETIC_FIELD_SENSOR_HPP_
17+
18+
#include <memory>
19+
#include <string>
20+
#include <vector>
21+
22+
#include "gmock/gmock.h"
23+
#include "semantic_components/magnetic_field_sensor.hpp"
24+
25+
// implementing and friending so we can access member variables
26+
class TestableMagneticFieldSensor : public semantic_components::MagneticFieldSensor
27+
{
28+
FRIEND_TEST(MagneticFieldSensorTest, validate_all);
29+
30+
public:
31+
// Use generation of interface names
32+
explicit TestableMagneticFieldSensor(const std::string & name) : MagneticFieldSensor(name) {}
33+
34+
virtual ~TestableMagneticFieldSensor() = default;
35+
};
36+
37+
class MagneticFieldSensorTest : public ::testing::Test
38+
{
39+
public:
40+
void SetUp()
41+
{
42+
full_interface_names_.reserve(size_);
43+
for (auto index = 0u; index < size_; ++index)
44+
{
45+
full_interface_names_.emplace_back(
46+
sensor_name_ + "/" + magnetic_field_interface_names_[index]);
47+
}
48+
}
49+
50+
void TearDown();
51+
52+
protected:
53+
const size_t size_ = 3;
54+
const std::string sensor_name_ = "test_magnetometer";
55+
std::array<double, 3> magnetic_field_values_ = {{4.4, 5.5, 6.6}};
56+
std::unique_ptr<TestableMagneticFieldSensor> magnetic_field_sensor_;
57+
58+
std::vector<std::string> full_interface_names_;
59+
const std::vector<std::string> magnetic_field_interface_names_ = {
60+
"magnetic_field.x", "magnetic_field.y", "magnetic_field.z"};
61+
};
62+
63+
#endif // TEST_MAGNETIC_FIELD_SENSOR_HPP_

doc/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ For details see the controller_manager section.
3131
* A new ``SemanticComponentCommandInterface`` semantic component provides capabilities analogous to the ``SemanticComponentInterface``, but for write-only devices (`#1945 <https://github.com/ros-controls/ros2_control/pull/1945>`_)
3232
* The new semantic command interface ``LedRgbDevice`` provides standard (command) interfaces for 3-channel LED devices (`#1945 <https://github.com/ros-controls/ros2_control/pull/1945>`_)
3333

34+
controller_interface
35+
********************
36+
* The new ``MagneticFieldSensor`` semantic component provides an interface for reading data from magnetometers. `(#2627 <https://github.com/ros-controls/ros2_control/pull/2627>`__)
37+
3438
controller_manager
3539
******************
3640
* URDF is now passed to controllers on init (`#1088 <https://github.com/ros-controls/ros2_control/pull/1088>`_)

hardware_interface/doc/semantic_components.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ List of existing ``SemanticComponentInterface`` `(link to header file) <https://
1212
* `IMUSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/imu_sensor.hpp>`__, used by :ref:`IMU Sensor Broadcaster <imu_sensor_broadcaster_userdoc>`
1313
* `ForceTorqueSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/force_torque_sensor.hpp>`__, used by :ref:`Force Torque Sensor Broadcaster <force_torque_sensor_broadcaster_userdoc>`
1414
* `GPSSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/gps_sensor.hpp>`__
15+
* `MagneticFieldSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/magnetic_field_sensor.hpp>`__
1516
* `PoseSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/pose_sensor.hpp>`__, used by :ref:`Pose Broadcaster <pose_broadcaster_userdoc>`
1617
* `RangeSensor <https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/controller_interface/include/semantic_components/range_sensor.hpp>`__, used by :ref:`Range Sensor Broadcaster <range_sensor_broadcaster_userdoc>`
1718

0 commit comments

Comments
 (0)