-
Notifications
You must be signed in to change notification settings - Fork 363
Proposal for solving multiple issues with implict interface in hardware. #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,19 +16,126 @@ | |
#define HARDWARE_INTERFACE__HANDLE_HPP_ | ||
|
||
#include <string> | ||
#include <vector> | ||
#include <utility> | ||
|
||
#include "hardware_interface/macros.hpp" | ||
#include "hardware_interface/visibility_control.h" | ||
|
||
#include <hardware_interface/hardware_info.hpp> | ||
|
||
namespace hardware_interface | ||
{ | ||
struct InterfaceConfiguration | ||
{ | ||
std::string joint_name; | ||
InterfaceInfo interface_info; | ||
}; | ||
|
||
/// A variant class used to store value on a given interface | ||
class Variant | ||
{ | ||
enum value_type | ||
{ | ||
boolean = 0, | ||
integer, | ||
floating, | ||
vector_boolean, | ||
vector_integer, | ||
vector_floating, | ||
}; | ||
|
||
public: | ||
Variant(bool value) | ||
{ | ||
value_type_ = value_type::boolean; | ||
bool_value_ = value; | ||
} | ||
|
||
Variant(int value) | ||
{ | ||
value_type_ = value_type::integer; | ||
int_value_ = value; | ||
} | ||
|
||
Variant(double value) | ||
{ | ||
value_type_ = value_type::floating; | ||
double_value_ = value; | ||
} | ||
|
||
Variant(const std::vector<bool> & value) | ||
{ | ||
value_type_ = value_type::vector_boolean; | ||
vector_bool_value_ = value; | ||
} | ||
|
||
Variant(const std::vector<int> & value) | ||
{ | ||
value_type_ = value_type::vector_integer; | ||
vector_int_value_ = value; | ||
} | ||
|
||
Variant(const std::vector<double> & value) | ||
{ | ||
value_type_ = value_type::vector_floating; | ||
vector_double_value_ = value; | ||
} | ||
|
||
void set_value(bool value) | ||
{ | ||
bool_value_ = value; | ||
} | ||
|
||
void set_value(int value) | ||
{ | ||
int_value_ = value; | ||
} | ||
|
||
void set_value(double value) | ||
{ | ||
double_value_ = value; | ||
} | ||
|
||
template <typename DataT, typename std::enable_if<std::is_integral<DataT>, bool> = true> | ||
DataT get_value() | ||
{ | ||
return bool_value_; | ||
} | ||
|
||
template <typename DataT, typename std::enable_if<std::is_integral<DataT>, int> = true> | ||
DataT get_value() | ||
{ | ||
return int_value_; | ||
} | ||
|
||
template <typename DataT, typename std::enable_if<std::is_floating_point<DataT>, double> = true> | ||
DataT get_value() | ||
{ | ||
return double_value_; | ||
} | ||
|
||
bool is_valid() | ||
{ | ||
// Check if nan or empty to return "false" | ||
} | ||
|
||
private: | ||
value_type value_type_; | ||
bool bool_value_; | ||
int int_value_; | ||
double double_value_; | ||
std::vector<bool> vector_bool_value_; | ||
std::vector<int> vector_int_value_; | ||
std::vector<double> vector_double_value_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you should add a member that would store whether this interface is initialized and a getter function.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would really like to be rid of initialized flag values and instead use RAII like it was meant to be used. All constructed objects are either valid or don't exist. We should use the type system for things like this. |
||
}; | ||
|
||
/// A handle used to get and set a value on a given interface. | ||
class ReadOnlyHandle | ||
{ | ||
public: | ||
ReadOnlyHandle( | ||
const std::string & name, const std::string & interface_name, double * value_ptr = nullptr) | ||
const std::string & name, const std::string & interface_name, Variant * value_ptr = nullptr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't fix the fundamental problem I see with the interfaces of ros2_control. With this change, you are still using a pointer to some memory in the hardware_interface for communication between the hardware_interface and the controllers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just don't know any other approach that would work will all the types we want to support and provides all the flexibility and strictness to manage this sensibly with controller manager. Using functions does not solve fully all the issues, but adds complexity on the users' side of the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using functions makes it explicit and not out of band. |
||
: name_(name), interface_name_(interface_name), value_ptr_(value_ptr) | ||
{ | ||
} | ||
|
@@ -62,23 +169,24 @@ class ReadOnlyHandle | |
|
||
const std::string get_full_name() const { return name_ + "/" + interface_name_; } | ||
|
||
double get_value() const | ||
template <typedef DataT> | ||
DataT get_value() const | ||
{ | ||
THROW_ON_NULLPTR(value_ptr_); | ||
return *value_ptr_; | ||
return value_ptr_->get_value(); | ||
} | ||
|
||
protected: | ||
std::string name_; | ||
std::string interface_name_; | ||
double * value_ptr_; | ||
Variant * value_ptr_; | ||
}; | ||
|
||
class ReadWriteHandle : public ReadOnlyHandle | ||
{ | ||
public: | ||
ReadWriteHandle( | ||
const std::string & name, const std::string & interface_name, double * value_ptr = nullptr) | ||
const std::string & name, const std::string & interface_name, Variant * value_ptr = nullptr) | ||
: ReadOnlyHandle(name, interface_name, value_ptr) | ||
{ | ||
} | ||
|
@@ -97,10 +205,11 @@ class ReadWriteHandle : public ReadOnlyHandle | |
|
||
virtual ~ReadWriteHandle() = default; | ||
|
||
void set_value(double value) | ||
template <typename DataT> | ||
void set_value(DataT value) | ||
{ | ||
THROW_ON_NULLPTR(this->value_ptr_); | ||
*this->value_ptr_ = value; | ||
this->value_ptr_->set_value(value); | ||
} | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,6 +97,10 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI | |
return CallbackReturn::SUCCESS; | ||
}; | ||
|
||
virtual std::vector<InterfaceConfiguration> export_state_interfaces_info() = 0; | ||
|
||
virtual std::vector<InterfaceConfiguration> export_command_interfaces_info() = 0; | ||
|
||
/// Exports all state interfaces for this hardware interface. | ||
/** | ||
* The state interfaces have to be created and transferred according | ||
|
@@ -166,7 +170,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI | |
* | ||
* \return return_type::OK if the read was successful, return_type::ERROR otherwise. | ||
*/ | ||
virtual return_type read() = 0; | ||
virtual return_type read(std::vector<Variant> & states) = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why a controller should deal with a failure of an interface that belongs to a hardware. Controller should do it's thing. The issue you actually have is missing functionality in controller manager. Everything is known for months already, but someone has to write some code at some point. Unfortunately, most of the users decide to do some external solutions instead of writing 50 lines of code in the controller manager. |
||
|
||
/// Write the current command values to the actuator. | ||
/** | ||
|
@@ -175,7 +179,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI | |
* | ||
* \return return_type::OK if the read was successful, return_type::ERROR otherwise. | ||
*/ | ||
virtual return_type write() = 0; | ||
virtual return_type write(std::vector<Variant> & commands) = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NoDiscard would be good here, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree NoDiscard should be used on these and the return here should be propagated to the controllers so the controller fails when the hardware it is commanding fails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not, and it will never be propagated to controllers. Controller manager should deal with this. (see comment above) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The controller is the interface to the user in many cases as the controllers create ros interfaces. This means that failures are communicated out of band from how external ros processes are interacting with ros2_control. I want to get rid of all of these sorts of "out of band", "side-affect", "implicit" communication channels and make the handling of errors part of the explicit path of the code. |
||
|
||
/// Get name of the actuator hardware. | ||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better name is needed. Maybe
InterfaceVariant
,HWInterfaceVariant
, orHardwareInterfaceVariant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to just use std::variant from c++17, it is a much nicer implementation of a sum type than this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, the problem with std::variant is that it does not support arrays or vectors – which we want to add in forceable future (cf. #490)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can't copy a vector without calling
new
, I don't understand why you need vectors when you can use template arguments to specify the size of sets of values.