-
Notifications
You must be signed in to change notification settings - Fork 3
Feature/pm 524 Generalize motor controller #284
base: develop
Are you sure you want to change the base?
Conversation
…me checks. Make some small fixes
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 wander what really in what sense the motor controller is more general then the IMC object. It seems that the MotorController object is just a IMC object where everything is virtual. I cannot imagine that other motor controllers are so similar.
march_hardware/include/march_hardware/imotioncube/motor_controller_state.h
Outdated
Show resolved
Hide resolved
Codecov Report
@@ Coverage Diff @@
## develop #284 +/- ##
===========================================
- Coverage 56.66% 56.63% -0.04%
===========================================
Files 88 90 +2
Lines 3150 3129 -21
===========================================
- Hits 1785 1772 -13
+ Misses 1365 1357 -8
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
Lemme explain why I think (most of) the methods of the MotorController class are necessary for any motor controller: All basic necessary functionality is covered by:
That leaves us with the methods
Does that explain your doubts? |
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.
Overall, I don't see how this solves any problems. This only creates more. I still have to be convinced of the whole idea of a generalized motor controller, since you only use 1 type, where each motor controller works in its own unique way. The way I see this be used is: a new motor controller gets implemented using the interface, but it becomes clear that it misses some functionality/needs to alter some functionality. The interface gets adapted to work with the new motor controller. However, in the process the IMC gets neglected and never used again, since we now have a new one. Ultimately the IMc gets removed and therefore the whole interface is not useful anymore. I see this as a detour in implementing a new motor controller with a lot of extra maintenance.
virtual MotorControllerStates getStates() = 0; | ||
|
||
virtual ActuationMode getActuationMode() const = 0; | ||
virtual uint16_t getSlaveIndex() const = 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.
What if a motor controller isn't a slave or doesn't use the master-slave structure.
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.
Added a docstring to explain
march_hardware/include/march_hardware/imotioncube/motor_controller.h
Outdated
Show resolved
Hide resolved
virtual double getAngleRadAbsolute() = 0; | ||
virtual double getAngleRadIncremental() = 0; | ||
virtual double getVelocityRadAbsolute() = 0; | ||
virtual double getVelocityRadIncremental() = 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.
What if my motor controller does not have either an absolute or incremental encoder?
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 thought about this; the incremental/absolute encoders are a characteristic of Joint rather than of the motor controller, so it would make more sense to move such specifications there. That would require me to subclass Joint into different types, which I preferred to avoid, especially since our current three options for motor controllers will probably be connected to both types of encoders. I think such a refactor of the joint class is better postponed to a moment in the future where an encoder is actually connected to something other than a motor controller.
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.
Hmm on second thought, I could move the calculations using both encoders downstream to the motor controller. Then these would simplify to getPosition() and getVelocity(). Downside would be that these calculations would be that this code would probably be duplicate in each different MotorController class, but that might again be resolvable by inheriting from a MultipleEncodersMotorControllers class that implements these methods common for motor controllers with both encoders.
That was a nice spam of hersenspinsels, let me know if it makes any sense and what you think.
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.
Decided that since the 3 motor controllers currently under consideration all have both an incremental and an absolute encoder, this change is not very useful for now. It won't be difficult to implement later on when such a motor controller is actually put to use.
virtual float getMotorCurrent() = 0; | ||
virtual float getMotorControllerVoltage() = 0; | ||
virtual float getMotorVoltage() = 0; | ||
virtual bool getIncrementalMorePrecise() const = 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.
This also seems like a very specific function that should not be inside the motor controller class. When I read this method call I cannot tell what it would do generally on most controllers.
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.
Added a docstring with explanation. This method is relevant as long as a motor controller has both an incremental and an absolute encoder.
return -1; | ||
} | ||
return this->imc_->getVelocityIUIncremental(); | ||
return this->controller_->getTorque(); |
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 will end horribly, if the controller_
is a nullptr
, always check you pointers before using them. I now see you do this a lot more, so be warned
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 build a check in hardware builder to ensure that every joint has a controller at startup, removing the need to check this every time during runtime.
PS previously, the check at startup was not there, while not everyone of the methods in joint did the nullptr check (e.g. Joint.getIMotionCubeState), so this should actually be an improvement
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.
What if the hardware is not initialized by the hardware builder, what if it contains a bug that results in creating a nullptr
? You can't assume that a check somewhere else will protect this class. I know not every method contained the check, but that is not an argument for completely removing 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.
Hmm do you think a check in the constructor suffice? That will also immediately cover for all the plebs that appearantly like to make methods without these checks. Also, it will make the code less bloated
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.
The constructor would only check this once. Adding a check here would ensure that it is always a valid pointer.
march_hardware/include/march_hardware/imotioncube/imotioncube.h
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.
I see you also started renaming things in the MarchRobot
class, but not actually changing anything. You are actually making everything more vague and abstract, which is not something we need for software specifically written for one type of hardware. Again, I am still not convinced. To me it seems like you renamed everything from IMotionCube
to MotorController
plus a few other fixes.
march_hardware/include/march_hardware/motor_controller/imotioncube/imotioncube_states.h
Outdated
Show resolved
Hide resolved
march_hardware/include/march_hardware/motor_controller/imotioncube/imotioncube_states.h
Outdated
Show resolved
Hide resolved
march_hardware/include/march_hardware/motor_controller/motor_controller_states.h
Outdated
Show resolved
Hide resolved
march_hardware/include/march_hardware/motor_controller/imotioncube/imotioncube_states.h
Outdated
Show resolved
Hide resolved
march_hardware/include/march_hardware/motor_controller/imotioncube/imotioncube_states.h
Outdated
Show resolved
Hide resolved
void resetMotorControllers(); | ||
|
||
void startEtherCAT(bool reset_imc); | ||
void startCommunication(bool reset_motor_controllers); | ||
|
||
void stopEtherCAT(); | ||
void stopCommunication(); | ||
|
||
int getMaxSlaveIndex(); | ||
|
||
bool hasValidSlaves(); | ||
|
||
bool isEthercatOperational(); | ||
bool isCommunicationOperational(); | ||
|
||
std::exception_ptr getLastEthercatException() const noexcept; | ||
std::exception_ptr getLastCommunicationException() const noexcept; | ||
|
||
void waitForPdo(); | ||
void waitForUpdate(); | ||
|
||
int getEthercatCycleTime() const; | ||
int getCycleTime() const; |
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's nice that you changed these names, however, that does not make them compatible with other motor controllers. The reset and start functions are implemented specifically for the IMotionCube, so you still have to change those methods.
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 was already hesitant about the removal of ethercat from these namings, because that indeed introduces a lot more vagueness. Hence I reverted them for now.
I do not really understand what you say about reset and start functions; the reset of the MarchRobot directly calls the reset of the MotorController, which should work for any controller. Similarly, the start method calls start for the ethercatMaster, which in turn simply calls initialize for each MotorController. This should work for any Ethercat motor controller. For a none-ethercat motor controller, a new communication master object should be created, with another implementation of the start method.
{ | ||
ROS_DEBUG("Resetting all IMotionCubes due to either: reset arg: %d or downloading of .sw fie: %d", reset_imc, |
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.
Again, nice that you changed these names, however, this makes everything so vague. You name it a motor controller, but in the same sentence use the .sw
files, which are specific for the IMotionCube.
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.
A good spot. I changed ".sw file" to "configuration file". Admittedly, that is a bit more vague, but still not to difficult to figure out if you know the system (and this is actually better for noobs that don't know what a .sw file is).
return -1; | ||
} | ||
return this->imc_->getVelocityIUIncremental(); | ||
return this->controller_->getTorque(); |
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.
What if the hardware is not initialized by the hardware builder, what if it contains a bug that results in creating a nullptr
? You can't assume that a check somewhere else will protect this class. I know not every method contained the check, but that is not an argument for completely removing it.
@TimBuckers @martijnvandermarel Here's an interesting issue |
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 support the idea that the implementation motor controllers should be more generic, because this would make it easier to implement new motor controllers. However, I am not sure if this is the best interface. Right now, it seems to me that the added layer of abstraction is created with just the iMOTIONCUBE in mind. Other motor controllers might not be able to support all the functionalities that are "required" to implement the MotorController
class. It might be beneficial to implement other motor controllers before deciding which methods and functionalities are shared between these implementations. This would avoid the creation of an abstraction that is too specific. Such an abstraction would lose its purpose.
std::unique_ptr<IMotionCube> imc_ = nullptr; | ||
std::unique_ptr<TemperatureGES> temperature_ges_ = nullptr; | ||
std::shared_ptr<MotorController> controller_ = nullptr; | ||
std::shared_ptr<TemperatureGES> temperature_ges_ = nullptr; |
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.
Why has this been changed to a shared_ptr
?
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.
Because I now pass a list of ethercat slaves to ethercat_master (so that march_robot does not need to know which joints are ethercat slaves, and because it makes more sense anyway). So now there are two or three pointers to a a Ges, on in the EthercatMaster, and one in one or two of the joints (previously, the two joints had two separate ges objects for the same ges).
/** | ||
* Initializes the ethercat train and starts a thread for the loop. | ||
* @throws HardwareException If not the configured amount of slaves was found | ||
* or they did not all reach operational state | ||
*/ | ||
bool start(std::vector<Joint>& joints); | ||
bool start(); |
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 explain why the joints
parameter has been removed and what the return value of this function indicates?
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.
Previously, the joints parameter was used by the ethercat master to access the ethercat slaves to call initSdo on each. The EthercatMaster is now passed a list of ethercat slaves at initiation, making joints parameter unnecessary. The return value is now explained in the docstring
@@ -68,7 +75,7 @@ class EthercatMaster | |||
/** | |||
* Configures the found slaves to operational state. | |||
*/ | |||
bool ethercatSlaveInitiation(std::vector<Joint>& joints); | |||
bool ethercatSlaveInitiation(); |
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 explain why the joints
parameter has been removed and what the return value of this function indicates?
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.
See above
@@ -26,107 +26,57 @@ class Joint | |||
/** | |||
* Initializes a Joint with motor controller and without temperature slave. | |||
*/ | |||
Joint(std::string name, int net_number, bool allow_actuation, std::unique_ptr<IMotionCube> imc); | |||
Joint(std::string name, int net_number, bool allow_actuation, std::shared_ptr<MotorController> controller); |
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.
Why has this been changed from a unique_ptr
to a shared_ptr
?
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.
Same reason for the ges, there is now one pointer in EthercatMaster, and one in the MarchRobot
@@ -196,7 +203,7 @@ HardwareBuilder::createIncrementalEncoder(const YAML::Node& incremental_encoder_ | |||
return std::make_unique<march::IncrementalEncoder>(resolution, transmission); | |||
} | |||
|
|||
std::unique_ptr<march::TemperatureGES> HardwareBuilder::createTemperatureGES(const YAML::Node& temperature_ges_config, | |||
std::shared_ptr<march::TemperatureGES> HardwareBuilder::createTemperatureGES(const YAML::Node& temperature_ges_config, |
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.
Why has this been changed to a shared_ptr
?
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.
Same
} | ||
|
||
std::shared_ptr<march::TemperatureGES> ges = | ||
std::make_unique<march::TemperatureGES>(march::Slave(slave_index, pdo_interface, sdo_interface), byte_offset); |
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.
Why are you using make_unique
if you want to create a shared_ptr
?
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.
Oopsie 😅
} | ||
|
||
std::unique_ptr<march::PowerDistributionBoard> HardwareBuilder::createPowerDistributionBoard( | ||
const YAML::Node& pdb, march::PdoInterfacePtr pdo_interface, march::SdoInterfacePtr sdo_interface) | ||
std::shared_ptr<march::PowerDistributionBoard> HardwareBuilder::createPowerDistributionBoard( |
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.
Why has this been changed to a shared_ptr
?
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.
Same
Closes PM-524
Requires fix/update-imc-states-msg branch in the march repo
Description
In this PR I generalize the hardware interface for different types of motor controllers. I created an abstract class MotorController that specifies an interface each motor controller needs to implement. I then moved some functionality down from Joint class to the motor controller. Most other changes are the renaming of variables from imc to (motor_)controller.
Currently, the iMotionCube states topic still publishes some imc specific information. I was unsure how I could generalize this, so I decided to postpone this to a different PR. Depending on what exactly the format of the corresponding information for other motor controllers is, I might try to generalize the usage of some of its fields. Alternatively, I could just add more fields for each of the unique states of a motor controller type, or create a different topic for each type. Let me know if you have any ideas.
Changes
Testing
Imma test on exo tomorrow