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