ros2_control - rolling
Loading...
Searching...
No Matches
handle.hpp
1// Copyright 2020 PAL Robotics S.L.
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 HARDWARE_INTERFACE__HANDLE_HPP_
16#define HARDWARE_INTERFACE__HANDLE_HPP_
17
18#include <fmt/compile.h>
19
20#include <algorithm>
21#include <cstdint>
22#include <cstdlib>
23#include <functional>
24#include <limits>
25#include <memory>
26#include <mutex>
27#include <optional>
28#include <shared_mutex>
29#include <stdexcept>
30#include <string>
31#include <type_traits>
32#include <typeinfo>
33#include <utility>
34#include <variant>
35
36#ifndef _WIN32
37#include <cxxabi.h>
38#endif
39
40#include "hardware_interface/hardware_info.hpp"
41#include "hardware_interface/introspection.hpp"
42#include "hardware_interface/lexical_casts.hpp"
43#include "hardware_interface/macros.hpp"
44
45#include "rclcpp/logging.hpp"
46
47namespace
48{
49#ifndef _WIN32
50template <typename T>
51std::string get_type_name()
52{
53 int status = 0;
54 std::unique_ptr<char[], void (*)(void *)> res{
55 abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status), std::free};
56 return (status == 0) ? res.get() : typeid(T).name();
57}
58#else
59// not supported on Windows, use typeid directly
60template <typename T>
61std::string get_type_name()
62{
63 return typeid(T).name();
64}
65#endif
66} // namespace
67
68namespace hardware_interface
69{
70
72class Handle
73{
74public:
75 explicit Handle(
76 const std::string & prefix_name, const std::string & interface_name,
77 const std::string & data_type = "double", const std::string & initial_value = "")
78 : prefix_name_(prefix_name),
79 interface_name_(interface_name),
80 handle_name_(prefix_name_ + "/" + interface_name_),
81 data_type_(data_type)
82 {
83 // we need to initialize according the type passed in interface description
84 if (data_type_ == hardware_interface::HandleDataType::DOUBLE)
85 {
86 try
87 {
88 value_ = initial_value.empty() ? std::numeric_limits<double>::quiet_NaN()
89 : hardware_interface::stod(initial_value);
90 }
91 catch (const std::invalid_argument & err)
92 {
93 throw std::invalid_argument(
94 fmt::format(
95 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
96 initial_value, handle_name_, data_type_.to_string()));
97 }
98 }
99 else if (data_type_ == hardware_interface::HandleDataType::FLOAT32)
100 {
101 try
102 {
103 value_ = initial_value.empty()
104 ? std::numeric_limits<float>::quiet_NaN()
105 : static_cast<float>(hardware_interface::stof(initial_value));
106 }
107 catch (const std::invalid_argument & err)
108 {
109 throw std::invalid_argument(
110 fmt::format(
111 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
112 initial_value, handle_name_, data_type_.to_string()));
113 }
114 }
115 else if (data_type_ == hardware_interface::HandleDataType::BOOL)
116 {
117 try
118 {
119 value_ = initial_value.empty() ? false : hardware_interface::parse_bool(initial_value);
120 }
121 catch (const std::invalid_argument & err)
122 {
123 throw std::invalid_argument(
124 fmt::format(
125 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
126 initial_value, handle_name_, data_type_.to_string()));
127 }
128 }
129 else if (data_type_ == hardware_interface::HandleDataType::UINT8)
130 {
131 try
132 {
133 value_ = initial_value.empty()
134 ? static_cast<uint8_t>(std::numeric_limits<uint8_t>::max())
135 : static_cast<uint8_t>(hardware_interface::stoui8(initial_value));
136 }
137 catch (const std::invalid_argument & err)
138 {
139 throw std::invalid_argument(
140 fmt::format(
141 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
142 initial_value, handle_name_, data_type_.to_string()));
143 }
144 }
145 else if (data_type_ == hardware_interface::HandleDataType::INT8)
146 {
147 try
148 {
149 value_ = initial_value.empty()
150 ? static_cast<int8_t>(std::numeric_limits<int8_t>::max())
151 : static_cast<int8_t>(hardware_interface::stoi8(initial_value));
152 }
153 catch (const std::invalid_argument & err)
154 {
155 throw std::invalid_argument(
156 fmt::format(
157 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
158 initial_value, handle_name_, data_type_.to_string()));
159 }
160 }
161 else if (data_type_ == hardware_interface::HandleDataType::UINT16)
162 {
163 try
164 {
165 value_ = initial_value.empty()
166 ? static_cast<uint16_t>(std::numeric_limits<uint16_t>::max())
167 : static_cast<uint16_t>(hardware_interface::stoui16(initial_value));
168 }
169 catch (const std::invalid_argument & err)
170 {
171 throw std::invalid_argument(
172 fmt::format(
173 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
174 initial_value, handle_name_, data_type_.to_string()));
175 }
176 }
177 else if (data_type_ == hardware_interface::HandleDataType::INT16)
178 {
179 try
180 {
181 value_ = initial_value.empty()
182 ? static_cast<int16_t>(std::numeric_limits<int16_t>::max())
183 : static_cast<int16_t>(hardware_interface::stoi16(initial_value));
184 }
185 catch (const std::invalid_argument & err)
186 {
187 throw std::invalid_argument(
188 fmt::format(
189 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
190 initial_value, handle_name_, data_type_.to_string()));
191 }
192 }
193 else if (data_type_ == hardware_interface::HandleDataType::UINT32)
194 {
195 try
196 {
197 value_ = initial_value.empty()
198 ? static_cast<uint32_t>(std::numeric_limits<uint32_t>::max())
199 : static_cast<uint32_t>(hardware_interface::stoui32(initial_value));
200 }
201 catch (const std::invalid_argument & err)
202 {
203 throw std::invalid_argument(
204 fmt::format(
205 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
206 initial_value, handle_name_, data_type_.to_string()));
207 }
208 }
209 else if (data_type_ == hardware_interface::HandleDataType::INT32)
210 {
211 try
212 {
213 value_ = initial_value.empty()
214 ? static_cast<int32_t>(std::numeric_limits<int32_t>::max())
215 : static_cast<int32_t>(hardware_interface::stoi32(initial_value));
216 }
217 catch (const std::invalid_argument & err)
218 {
219 throw std::invalid_argument(
220 fmt::format(
221 FMT_COMPILE("Invalid initial value: '{}' parsed for interface: '{}' with type: '{}'"),
222 initial_value, handle_name_, data_type_.to_string()));
223 }
224 }
225 else
226 {
227 throw std::runtime_error(
228 fmt::format(
229 FMT_COMPILE("Invalid data type: '{}' for interface: {}. Check supported types."),
230 data_type, handle_name_));
231 }
232 }
233
234 explicit Handle(const InterfaceDescription & interface_description)
235 : Handle(
236 interface_description.get_prefix_name(), interface_description.get_interface_name(),
237 interface_description.get_data_type_string(),
238 interface_description.interface_info.initial_value)
239 {
240 }
241
242 [[deprecated("Use InterfaceDescription for initializing the Interface")]]
243
244 explicit Handle(const std::string & interface_name)
245 : interface_name_(interface_name), handle_name_("/" + interface_name_)
246 {
247 }
248
249 [[deprecated("Use InterfaceDescription for initializing the Interface")]]
250
251 explicit Handle(const char * interface_name)
252 : interface_name_(interface_name), handle_name_("/" + interface_name_)
253 {
254 }
255
256 Handle(const Handle & other) noexcept { copy(other); }
257
258 Handle & operator=(const Handle & other)
259 {
260 if (this != &other)
261 {
262 copy(other);
263 }
264 return *this;
265 }
266
267 Handle(Handle && other) noexcept { swap(*this, other); }
268
269 Handle & operator=(Handle && other)
270 {
271 swap(*this, other);
272 return *this;
273 }
274
275 virtual ~Handle() = default;
276
277 const std::string & get_name() const { return handle_name_; }
278
279 const std::string & get_interface_name() const { return interface_name_; }
280
281 const std::string & get_prefix_name() const { return prefix_name_; }
282
293 template <typename T = double>
294 [[nodiscard]] std::optional<T> get_optional() const
295 {
296 std::shared_lock<std::shared_mutex> lock(handle_mutex_, std::try_to_lock);
297 return get_optional<T>(lock);
298 }
310 template <typename T = double>
311 [[nodiscard]] std::optional<T> get_optional(std::shared_lock<std::shared_mutex> & lock) const
312 {
313 if (!lock.owns_lock())
314 {
315 return std::nullopt;
316 }
317 // BEGIN (Handle export change): for backward compatibility
318 // TODO(saikishor) return value_ if old functionality is removed
319 if constexpr (std::is_same_v<T, double>)
320 {
321 switch (data_type_)
322 {
323 case HandleDataType::DOUBLE:
324 return std::get<double>(value_);
325 case HandleDataType::BOOL:
326 // TODO(christophfroehlich): replace with RCLCPP_WARN_ONCE once
327 // https://github.com/ros2/rclcpp/issues/2587
328 // is fixed
329 if (!notified_)
330 {
331 RCLCPP_WARN(
332 rclcpp::get_logger(get_name()), "%s",
333 fmt::format(
334 FMT_COMPILE(
335 "Casting bool to double for interface '{}'. Better use get_optional<bool>()."),
336 get_name())
337 .c_str());
338 notified_ = true;
339 }
340 return static_cast<double>(std::get<bool>(value_));
341 case HandleDataType::FLOAT32: // fallthrough
342 case HandleDataType::UINT8: // fallthrough
343 case HandleDataType::INT8: // fallthrough
344 case HandleDataType::UINT16: // fallthrough
345 case HandleDataType::INT16: // fallthrough
346 case HandleDataType::UINT32: // fallthrough
347 case HandleDataType::INT32: // fallthrough
348 throw std::runtime_error(
349 fmt::format(
350 FMT_COMPILE(
351 "Data type: '{}' will not be casted to double for interface: {}. Use "
352 "get_optional<{}>() instead."),
353 data_type_.to_string(), get_name(), data_type_.to_string()));
354 default:
355 throw std::runtime_error(
356 fmt::format(
357 FMT_COMPILE("Data type: '{}' cannot be casted to double for interface: {}"),
358 data_type_.to_string(), get_name()));
359 }
360 }
361 try
362 {
363 return std::get<T>(value_);
364 }
365 catch (const std::bad_variant_access & err)
366 {
367 throw std::runtime_error(
368 fmt::format(
369 FMT_COMPILE("Invalid data type: '{}' access for interface: {} expected: '{}'"),
370 get_type_name<T>(), get_name(), data_type_.to_string()));
371 }
372 // END
373 }
374
387 template <
388 typename T, typename = std::enable_if_t<
389 !std::is_same_v<std::decay_t<T>, std::shared_lock<std::shared_mutex>>>>
390 [[nodiscard]] bool get_value(T & value, bool wait_for_lock) const
391 {
392 if (wait_for_lock)
393 {
394 std::shared_lock<std::shared_mutex> lock(handle_mutex_);
395 return get_value(lock, value);
396 }
397 else
398 {
399 std::shared_lock<std::shared_mutex> lock(handle_mutex_, std::try_to_lock);
400 return get_value(lock, value);
401 }
402 }
403
415 template <typename T>
416 [[nodiscard]] bool set_value(const T & value, bool wait_for_lock)
417 {
418 if (wait_for_lock)
419 {
420 std::unique_lock<std::shared_mutex> lock(handle_mutex_);
421 return set_value(lock, value);
422 }
423 else
424 {
425 std::unique_lock<std::shared_mutex> lock(handle_mutex_, std::try_to_lock);
426 return set_value(lock, value);
427 }
428 }
429
441 template <typename T>
442 [[nodiscard]] bool set_value(const T & value)
443 {
444 std::unique_lock<std::shared_mutex> lock(handle_mutex_, std::try_to_lock);
445 return set_value(lock, value);
446 }
447
460 template <typename T>
461 [[nodiscard]] bool set_value(std::unique_lock<std::shared_mutex> & lock, const T & value)
462 {
463 if (!lock.owns_lock())
464 {
465 return false;
466 }
467 if (!std::holds_alternative<T>(value_))
468 {
469 throw std::runtime_error(
470 fmt::format(
471 FMT_COMPILE("Invalid data type: '{}' access for interface: {} expected: '{}'"),
472 get_type_name<T>(), get_name(), data_type_.to_string()));
473 }
474 value_ = value;
475 return true;
476 }
477
478 std::shared_mutex & get_mutex() const { return handle_mutex_; }
479
480 HandleDataType get_data_type() const { return data_type_; }
481
483 bool is_castable_to_double() const { return data_type_.is_castable_to_double(); }
484
485 bool is_valid() const { return !std::holds_alternative<std::monostate>(value_); }
486
487protected:
497 template <typename T>
498 [[nodiscard]] bool get_value(std::shared_lock<std::shared_mutex> & lock, T & value) const
499 {
500 if (!lock.owns_lock())
501 {
502 return false;
503 }
504 // BEGIN (Handle export change): for backward compatibility
505 // TODO(saikishor) get value_ if old functionality is removed
506 if constexpr (std::is_same_v<T, double>)
507 {
508 switch (data_type_)
509 {
510 case HandleDataType::DOUBLE:
511 value = std::get<double>(value_);
512 return true;
513 case HandleDataType::BOOL:
514 // TODO(christophfroehlich): replace with RCLCPP_WARN_ONCE once
515 // https://github.com/ros2/rclcpp/issues/2587
516 // is fixed
517 if (!notified_)
518 {
519 RCLCPP_WARN(
520 rclcpp::get_logger(get_name()),
521 "Casting bool to double for interface: %s. Better use get_optional<bool>().",
522 get_name().c_str());
523 notified_ = true;
524 }
525 value = static_cast<double>(std::get<bool>(value_));
526 return true;
527 case HandleDataType::FLOAT32: // fallthrough
528 case HandleDataType::UINT8: // fallthrough
529 case HandleDataType::INT8: // fallthrough
530 case HandleDataType::UINT16: // fallthrough
531 case HandleDataType::INT16: // fallthrough
532 case HandleDataType::UINT32: // fallthrough
533 case HandleDataType::INT32: // fallthrough
534 throw std::runtime_error(
535 fmt::format(
536 FMT_COMPILE(
537 "Data type: '{}' will not be casted to double for interface: {}. Use "
538 "get_optional<{}>() instead."),
539 data_type_.to_string(), get_name(), data_type_.to_string()));
540 default:
541 throw std::runtime_error(
542 fmt::format(
543 FMT_COMPILE("Data type: '{}' cannot be casted to double for interface: {}"),
544 data_type_.to_string(), get_name()));
545 }
546 }
547 try
548 {
549 value = std::get<T>(value_);
550 return true;
551 }
552 catch (const std::bad_variant_access & err)
553 {
554 throw std::runtime_error(
555 fmt::format(
556 FMT_COMPILE("Invalid data type: '{}' access for interface: {} expected: '{}'"),
557 get_type_name<T>(), get_name(), data_type_.to_string()));
558 }
559 }
560
561private:
562 void copy(const Handle & other) noexcept
563 {
564 std::scoped_lock lock(other.handle_mutex_, handle_mutex_);
566 interface_name_ = other.interface_name_;
567 handle_name_ = other.handle_name_;
568 value_ = other.value_;
569 data_type_ = other.data_type_;
570 }
571
572 void swap(Handle & first, Handle & second) noexcept
573 {
574 std::scoped_lock lock(first.handle_mutex_, second.handle_mutex_);
575 std::swap(first.prefix_name_, second.prefix_name_);
576 std::swap(first.interface_name_, second.interface_name_);
577 std::swap(first.handle_name_, second.handle_name_);
578 std::swap(first.value_, second.value_);
579 std::swap(first.data_type_, second.data_type_);
580 }
581
582protected:
584 std::string prefix_name_;
585 std::string interface_name_;
586 std::string handle_name_;
587 HANDLE_DATATYPE value_ = std::monostate{};
588 HandleDataType data_type_ = HandleDataType::DOUBLE;
589 // END
590 mutable std::shared_mutex handle_mutex_;
591
592private:
593 // TODO(christophfroehlich): remove once
594 // https://github.com/ros2/rclcpp/issues/2587
595 // is fixed
596 mutable bool notified_ = false;
597};
598
599class StateInterface : public Handle
600{
601public:
602 explicit StateInterface(const InterfaceDescription & interface_description)
603 : Handle(interface_description)
604 {
605 }
606
607 void registerIntrospection() const
608 {
609 if (!is_valid())
610 {
611 RCLCPP_WARN(
612 rclcpp::get_logger(get_name()),
613 "Cannot register state introspection for state interface: %s without a valid value "
614 "pointer or initialized value.",
615 get_name().c_str());
616 return;
617 }
618 if (data_type_.is_castable_to_double())
619 {
620 std::function<double()> f = [this]() { return data_type_.cast_to_double(value_); };
621 DEFAULT_REGISTER_ROS2_CONTROL_INTROSPECTION("state_interface." + get_name(), f);
622 }
623 }
624
625 void unregisterIntrospection() const
626 {
627 if (is_valid() && (data_type_.is_castable_to_double()))
628 {
629 DEFAULT_UNREGISTER_ROS2_CONTROL_INTROSPECTION("state_interface." + get_name());
630 }
631 }
632
633 StateInterface(const StateInterface & other) = default;
634
635 StateInterface(StateInterface && other) = default;
636
637 using Handle::Handle;
638
639 using SharedPtr = std::shared_ptr<StateInterface>;
640 using ConstSharedPtr = std::shared_ptr<const StateInterface>;
641};
642
644{
645public:
646 explicit CommandInterface(const InterfaceDescription & interface_description)
647 : Handle(interface_description)
648 {
649 }
651
656 CommandInterface(const CommandInterface & other) = delete;
657
658 CommandInterface(CommandInterface && other) = default;
659
660 void set_on_set_command_limiter(std::function<double(double, bool &)> on_set_command_limiter)
661 {
662 on_set_command_limiter_ = on_set_command_limiter;
663 }
664
666
670 template <typename T>
671 [[nodiscard]] bool set_limited_value(const T & value)
672 {
673 if constexpr (std::is_same_v<T, double>)
674 {
675 return set_value(on_set_command_limiter_(value, is_command_limited_));
676 }
677 else
678 {
679 return set_value(value);
680 }
681 }
682
683 const bool & is_limited() const { return is_command_limited_; }
684
685 void registerIntrospection() const
686 {
687 if (!is_valid())
688 {
689 RCLCPP_WARN(
690 rclcpp::get_logger(get_name()),
691 "Cannot register command introspection for command interface: %s without a valid value "
692 "pointer or initialized value.",
693 get_name().c_str());
694 return;
695 }
696 if (data_type_.is_castable_to_double())
697 {
698 std::function<double()> f = [this]() { return data_type_.cast_to_double(value_); };
699 DEFAULT_REGISTER_ROS2_CONTROL_INTROSPECTION("command_interface." + get_name(), f);
700 DEFAULT_REGISTER_ROS2_CONTROL_INTROSPECTION(
701 "command_interface." + get_name() + ".is_limited", &is_command_limited_);
702 }
703 }
704
705 void unregisterIntrospection() const
706 {
707 if (is_valid() && data_type_.is_castable_to_double())
708 {
709 DEFAULT_UNREGISTER_ROS2_CONTROL_INTROSPECTION("command_interface." + get_name());
710 DEFAULT_UNREGISTER_ROS2_CONTROL_INTROSPECTION(
711 "command_interface." + get_name() + ".is_limited");
712 }
713 }
714
715 using Handle::Handle;
716
717 using SharedPtr = std::shared_ptr<CommandInterface>;
718
719private:
720 bool is_command_limited_ = false;
721 std::function<double(double, bool &)> on_set_command_limiter_ =
722 [](double value, bool & is_limited)
723 {
724 is_limited = false;
725 return value;
726 };
727};
728
729} // namespace hardware_interface
730
731#endif // HARDWARE_INTERFACE__HANDLE_HPP_
Definition handle.hpp:644
bool set_limited_value(const T &value)
A setter for the value of the command interface that triggers the limiter.
Definition handle.hpp:671
CommandInterface(const CommandInterface &other)=delete
CommandInterface copy constructor is actively deleted.
Definition hardware_info.hpp:158
bool is_castable_to_double() const
Check if the HandleDataType can be casted to double.
Definition hardware_info.hpp:262
double cast_to_double(const HANDLE_DATATYPE &value) const
Cast the given value to double.
Definition hardware_info.hpp:288
A handle used to get and set a value on a given interface.
Definition handle.hpp:73
bool set_value(const T &value)
Set the value of the handle.
Definition handle.hpp:442
bool get_value(std::shared_lock< std::shared_mutex > &lock, T &value) const
Get the value of the handle.
Definition handle.hpp:498
std::optional< T > get_optional(std::shared_lock< std::shared_mutex > &lock) const
Get the value of the handle.
Definition handle.hpp:311
bool get_value(T &value, bool wait_for_lock) const
Get the value of the handle.
Definition handle.hpp:390
bool is_castable_to_double() const
Returns true if the handle data type can be casted to double.
Definition handle.hpp:483
std::string prefix_name_
Definition handle.hpp:584
bool set_value(std::unique_lock< std::shared_mutex > &lock, const T &value)
Set the value of the handle.
Definition handle.hpp:461
bool set_value(const T &value, bool wait_for_lock)
Set the value of the handle.
Definition handle.hpp:416
std::optional< T > get_optional() const
Get the value of the handle.
Definition handle.hpp:294
Definition handle.hpp:600
Definition mujoco_system_interface.hpp:57
std::variant< std::monostate, double, float, bool, uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t > HANDLE_DATATYPE
Definition hardware_info.hpp:156
float stof(const std::string &s)
Helper function to convert a std::string to float in a locale-independent way.
Definition lexical_casts.cpp:116
double stod(const std::string &s)
Helper function to convert a std::string to double in a locale-independent way.
Definition lexical_casts.cpp:107
bool parse_bool(const std::string &bool_string)
Parse a boolean value from a string.
Definition lexical_casts.cpp:134
Definition hardware_info.hpp:327
InterfaceInfo interface_info
Definition hardware_info.hpp:343
std::string initial_value
(Optional) Initial value of the interface.
Definition hardware_info.hpp:55