-
Notifications
You must be signed in to change notification settings - Fork 21
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
469 task rewrite thruster interface #492
base: develop
Are you sure you want to change the base?
Conversation
…erly. I still have to figure out how to remove default initialization class field from the header file. Apparently the code was relying on default values for default constructor and so on on multiple levels, including constructor default values, class default values, declare_parameter default values creating a total mess. Removed almost all of them, i'm not able to remove the last one: driver class default params. If I do, i get a compilation error saying that my constructor called from outside doesnt match with the constructor specified in the class? idk. But somehow executes my constructor because substitutes the default values, but if i call only mine, doesnt compile. BOH. Mi sa che vado a letto ciao.
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.
Remove the python package and change the name of the new cpp package back to the original name
...uster_interface_auv_cpp/include/thruster_interface_auv_cpp/thruster_interface_auv_driver.hpp
Outdated
Show resolved
Hide resolved
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.
document
} | ||
|
||
// Select the appropriate coefficients based on the operational voltage | ||
auto left_coeffs = COEFFS[SYSTEM_OPERATIONAL_VOLTAGE]["LEFT"]; |
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.
Try to avoid using auto in general. Especially when it is not easy to deduce what type it is
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 suggest following ES.11: Use auto to avoid redundant repetition of type names
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.
that variable should be a std::vector, like the other parameters passed as std::vector. The problem is that .as_integer_array() methods returns a long int and to do what we want to do i think it would require an explicit cast from long int to int that, imo, looks like inelegant and verbose. So i use auto. If i try to compile using explicit typing i get this:
error: conversion from ‘vector<long int,allocator>’ to non-scalar type ‘vector<int,allocator>’ requested
34 | this->get_parameter("propulsion.thrusters.thruster_to_pin_mapping")
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 | .as_integer_array();
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.
To be fair, if you end up not caring about system operational voltage, you can probably instantiate the left/right coefficients in the constructor?
Also, if you implement my suggested change and let your input be a parameter struct of some sorts, you can do this casting / validation immediately in the constructor.
Note that while long int -> int is a narrowing conversion, the thruster-to-pin mapping int will never be larger than what a normal int can hold, so I would actually suggest doing this cast
motion/thruster_interface_auv_cpp/src/thruster_interface_auv_driver.cpp
Outdated
Show resolved
Hide resolved
...thruster_interface_auv_cpp/include/thruster_interface_auv_cpp/thruster_interface_auv_ros.hpp
Outdated
Show resolved
Hide resolved
motion/thruster_interface_auv_cpp/src/thruster_interface_auv_ros.cpp
Outdated
Show resolved
Hide resolved
|
||
ThrusterInterfaceAUVNode::ThrusterInterfaceAUVNode() : Node("thruster_interface_auv_node") { | ||
|
||
//create a subscriber that takes data from thruster forces and publisher for debugging |
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.
Name of subscriber and publisher should be exposed as a ros param and read from a config file
//publish PWM values for debugging | ||
std_msgs::msg::Int16MultiArray pwm_message; | ||
pwm_message.data = thruster_pwm_array; | ||
thruster_pwm_publisher_->publish(pwm_message); |
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.
Should have a ros param boolean flag that only publishes if the flag is set so we dont publish when not needed. The boolean flag can also conditionally initialize the publisher so we don't initialize it if not needed. It would be nice to be able to toggle this during runtime. You can have a look at https://github.com/vortexntnu/vortex-image-filtering/blob/main/image-filtering/src/image_filtering_ros.cpp for inspiration on how this can be done
Also update the thrust_update_rate: 10.0 # [Hz] in orca.yaml |
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.
First pass of the C++ stuff.
As a general note, you might want to remove most of the inline comments. As a general rule of thumb, you should have a very good reason for adding inline codes (if you can't avoid some bit magic or some specific equation etc), as the code itself should do the explaining: Comments may lie (and/or become outdated), but the code wont!
This can most notably be seen in the suggestions to use std::algorithm, as that makes things a lot more explicit (especially when defining the lambdas outside the algorithm call and giving it a fitting name!
Also note that the suggestions here are free-balled, so make sure everything compiles and works as expected 😅
ThrusterInterfaceAUVDriver( | ||
int I2C_BUS, | ||
int PICO_I2C_ADDRESS, | ||
double SYSTEM_OPERATIONAL_VOLTAGE, | ||
const std::vector<int>& THRUSTER_MAPPING, | ||
const std::vector<int>& THRUSTER_DIRECTION, | ||
const std::vector<int>& THRUSTER_PWM_OFFSET, | ||
const std::vector<int>& PWM_MIN, | ||
const std::vector<int>& PWM_MAX, | ||
const std::map<int, std::map<std::string, std::vector<double>>>& COEFFS | ||
); |
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.
This is a constructor that's in the category of "too big". I would suggest making structs to hold some of the data, like
struct ThrusterParameters
{
std::uint8_t thruster_no;
std::uint8_t direction;
std::uint16_t pwm_offset;
std::uint16_t pwm_min;
std::uint16_t pwm_max;
};
then passing in const std::vector<ThrusterParameters>&
You might find you can do something similar with the other parameters as well!
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 a side note, good that you're passing the vectors/map by const reference!
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.
Another side note, when constructing the ThrusterParameters
objects, you can do additional checks to ensure that values are within legal range!
int bus_fd; | ||
int I2C_BUS; | ||
int PICO_I2C_ADDRESS; | ||
int SYSTEM_OPERATIONAL_VOLTAGE; | ||
|
||
std::vector<int> THRUSTER_MAPPING; | ||
std::vector<int> THRUSTER_DIRECTION; | ||
std::vector<int> THRUSTER_PWM_OFFSET; | ||
std::vector<int> PWM_MIN; | ||
std::vector<int> PWM_MAX; | ||
|
||
std::map<int, std::map<std::string, std::vector<double>>> COEFFS; |
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.
Avoid ALL_CAPS name except for static constexpr
member variables. Use either the m_ prefix or _ suffix (i.e. m_bus_fd or bus_fd_), not sure what your conventions are 😄
ThrusterInterfaceAUVDriver(); | ||
~ThrusterInterfaceAUVDriver(); |
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.
-
Do you need
ThrusterInterfaceAUVDriver
to be default constructable? It appears to me that you'd explicitly want it not to be. -
cppcoreguidelines say
E.16: Destructors, deallocation, swap, and exception type copy/move construction must never fail
, might be a good idea to explicitly define the destructor asnoexcept
, i.e. as~ThrusterInterfaceAUVDriver() noexcept;
, depending on your conventions. (I think it's handled implicitly by default?)
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.
yeah we dont actively use this in the code, it's there such that it compiles. This is because in the .hpp file for the node class i declare a driver class as field member of the node class and doing that it triggers a constructor, requesting me to create an "empty" constructor to handle this. Is there a better way to do it?
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.
Ah, you'd want to hold the ThrusterInterfaceAUVDriver object in a unique_ptr. That's the usual way of dealing with classes that need other (fairly big) class objects as part of their members. Especially in this case, where having a default-constructed ThrusterInterfaceAUVDriver is not "allowed" (in the sense that it will not be a valid object unless you make it with the appropriate constructor) So something like
std::unique_ptr<ThrusterInterfaceAUVDriver> thruster_interface_
And then using std::make_unique
where you define it. That will allow you to remove the default constructor.
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.
could you be more specific? What i did (and is not compiling with the same errors as when i hadn't the default constructor) is replacing
ThrusterInterfaceAUVDriver thruster_driver_
withstd::unique_ptr<ThrusterInterfaceAUVDriver> thruster_driver_
as member of the class "blablabla_node"- removing the default constructor
ThrusterInterfaceAUVDriver();
- and replacing the line of code that instantiate the object passing the arguments with
this->thruster_driver_ = std::make_unique<ThrusterInterfaceAUVDriver>(arguments....
The error is error: no declaration matches ‘ThrusterInterfaceAUVDriver::ThrusterInterfaceAUVDriver()’ 3 | ThrusterInterfaceAUVDriver::ThrusterInterfaceAUVDriver() {} | ^~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /home/alberto/vortex/auto_ws/src/vortex-auv/motion/thruster_interface_auv/src/thruster_interface_auv_driver.cpp:1: /home/alberto/vortex/auto_ws/src/vortex-auv/motion/thruster_interface_auv/include/thruster_interface_auv/thruster_interface_auv_driver.hpp:30:7: note: candidates are: ‘ThrusterInterfaceAUVDriver::ThrusterInterfaceAUVDriver(const ThrusterInterfaceAUVDriver&)’ 30 | class ThrusterInterfaceAUVDriver { | ^~~~~~~~~~~~~~~~~~~~~~~~~~ /home/alberto/vortex/auto_ws/src/vortex-auv/motion/thruster_interface_auv/include/thruster_interface_auv/thruster_interface_auv_driver.hpp:57:5: note: ‘ThrusterInterfaceAUVDriver::ThrusterInterfaceAUVDriver(short int, int, const std::vector<short int>&, const std::vector<short int>&, const std::vector<int>&, const std::vector<int>&, const std::vector<double>&, const std::vector<double>&)’ 57 | ThrusterInterfaceAUVDriver(short I2C_BUS, | ^~~~~~~~~~~~~~~~~~~~~~~~~~ /home/alberto/vortex/auto_ws/src/vortex-auv/motion/thruster_interface_auv/include/thruster_interface_auv/thruster_interface_auv_driver.hpp:30:7: note: ‘class ThrusterInterfaceAUVDriver’ defined here 30 | class ThrusterInterfaceAUVDriver { | ^~~~~~~~~~~~~~~~~~~~~~~~~~
if (SYSTEM_OPERATIONAL_VOLTAGE < 11.0) { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 10; | ||
} else if (SYSTEM_OPERATIONAL_VOLTAGE < 13.0) { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 12; | ||
} else if (SYSTEM_OPERATIONAL_VOLTAGE < 15.0) { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 14; | ||
} else if (SYSTEM_OPERATIONAL_VOLTAGE < 17.0) { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 16; | ||
} else if (SYSTEM_OPERATIONAL_VOLTAGE < 19.0) { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 18; | ||
} else { | ||
this->SYSTEM_OPERATIONAL_VOLTAGE = 20; | ||
} |
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 would suggest creating a free function for this, maybe something like
int discretize_system_operational_voltage(const double voltage)
{
// TODO: Short description of equation since it's kinda hard to digest :)
return std::min<int>(20.0, std::max(10.0, 2.0 * std::floor((voltage + 1.0) / 2.0)));
}
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.
completely removed this part from the code since we discovered there's no handling on the SYSTEM OP VOLTAGE. We dont read it from any topic since we dont measure it, so this piece becomes an unuseful piece of trash. Now we dont pass SYSTEM OP VOLTAGE anymore since it's 16V and not something else. ALWAYS. This mean we dont need anymore the map containing the pair of coeffs for any of them, just the 16V ones. Left all the coeffs in the yaml file for future uses, removed them from the code for saving resources
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.
Would it make sense to subscribe to a voltage topic and do this lookup for every thrust output then?
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.
it would make sense if we had a way to measure it but we don't. And voltage always stays around 16V, maybe it's 15.5 but it rounds up to 16 so anyway would result in using the 16V values.
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.
Fair enough, then there's not much you can do! However, not having a battery voltage measurement available is major haram, might want to whip electronics/embedded to get that up and running. Speaking from experience, if you do not have an alarm system set up to kill everything when the battery voltage dips too low for too long, you WILL destroy batteries 😂
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.
('im totally not sure about this @Andeshog correct me) but we should have a BMS, it's just that's it's purely enclosed in his purpose and doesnt talk with the other systems and so we dont have a "digital" access on it that we could use for this purpose.
if (bus_fd < 0) { | ||
std::cerr << "ERROR: Failed to open I2C bus " << I2C_BUS << std::endl; | ||
} |
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.
Maybe the constructor should throw if you fail to open the I2C bus?
std::vector<double> forces_in_kg(thruster_forces_array.size()); | ||
for (size_t i = 0; i < thruster_forces_array.size(); ++i) { | ||
forces_in_kg[i] = thruster_forces_array[i] / 9.80665; | ||
} |
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.
Avoid "raw loops" like the plague (i.e. for loops that have the type i = 0; i < ....
syntax)😨
The #include library has a LOT of really nice functions you can use instead. In this case (defining the lambda outside lets us name it as well
auto to_kg = [](double force) { return force / 9.80665; };
std::transform(thruster_forces_array.begin(), thruster_forces_array.end(), forces_in_kg.begin(), to_kg);
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 put it in the local scope of the driver class as static constexpr directly in the .hpp class, instead of declaring it and defining it in the .cpp, since it's a single line of code. Is it okay? Compiling is compiling and working too.
motion/thruster_interface_auv_cpp/src/thruster_interface_auv_driver.cpp
Outdated
Show resolved
Hide resolved
try { | ||
send_data_to_escs(thruster_pwm_array); | ||
} catch (...) { | ||
std::cerr << "ERROR: Failed to send PWM values" << std::endl; | ||
} |
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.
EDIT: The original link here doesn't really apply, but instead;
Catching all (...) removes all info about what error was thrown. It would be nice to at least catch std::exception and print the value resulting from it, so you know what failed
uint8_t i2c_data_array[16]; | ||
for (size_t i = 0; i < thruster_pwm_array.size(); ++i) { | ||
i2c_data_array[2*i] = (thruster_pwm_array[i] >> 8) & 0xFF; // MSB | ||
i2c_data_array[2*i + 1] = thruster_pwm_array[i] & 0xFF; // LSB | ||
} |
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.
Suggestion:
constexpr std::size_t i2c_data_size = 8 * 2; // 8 thrusters * (1xMSB + 1xLSB)
std::uint8_t i2c_data_array[i2c_data_size];
auto pwm_to_i2c_data = [](std::int16_t pwm) -> std::array<std::uint8_t, 2> {
return {static_cast<std::uint8_t>((pwm >> 8) & 0xFF), static_cast<std::uint8_t>(pwm & 0xFF)};
};
std::transform(thruster_pwm_array.begin(), thruster_pwm_array.end(), i2c_data_array, pwm_to_i2c_data);
No description provided.