Skip to content
This repository has been archived by the owner on Dec 1, 2020. It is now read-only.

Feature/pm 524 Generalize motor controller #284

Draft
wants to merge 35 commits into
base: develop
Choose a base branch
from

Conversation

Roelemans
Copy link
Contributor

@Roelemans Roelemans commented Aug 11, 2020

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

  • Created abstract MotorController class that is implemented by IMotionCube class.
  • Moved some functionality down to IMotionCube
  • Renamed lots of variables to (motor_)controller
  • Check joint for existence of motor controller once during startup and remove all runtime checks

Testing

Imma test on exo tomorrow

@Roelemans Roelemans self-assigned this Aug 11, 2020
@Roelemans Roelemans added the not-tested-on-march When a PR has not yet been tested on March label Aug 11, 2020
Copy link
Contributor

@RutgerVanBeek RutgerVanBeek left a 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.

@codecov
Copy link

codecov bot commented Aug 11, 2020

Codecov Report

Merging #284 into develop will decrease coverage by 0.03%.
The diff coverage is 42.42%.

Impacted file tree graph

@@             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     
Flag Coverage Δ
#production 37.49% <38.31%> (+0.26%) ⬆️
#test 98.97% <100.00%> (-0.22%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...ude/march_hardware/imotioncube/imotioncube_state.h 0.00% <0.00%> (ø)
...arch_hardware/imotioncube/motor_controller_state.h 0.00% <0.00%> (ø)
march_hardware/src/ethercat/ethercat_master.cpp 5.29% <0.00%> (ø)
...arch_hardware_interface/march_hardware_interface.h 0.00% <ø> (ø)
...ardware_interface/src/march_hardware_interface.cpp 0.00% <0.00%> (ø)
march_hardware/src/imotioncube/imotioncube.cpp 11.36% <15.38%> (-0.15%) ⬇️
march_hardware/src/march_robot.cpp 20.58% <25.00%> (ø)
march_hardware/include/march_hardware/joint.h 57.89% <80.00%> (+17.89%) ⬆️
march_hardware/src/joint.cpp 86.20% <82.60%> (+22.73%) ⬆️
...e/include/march_hardware/imotioncube/imotioncube.h 50.00% <100.00%> (ø)
... and 11 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3876beb...9b5cc5c. Read the comment docs.

@Roelemans
Copy link
Contributor Author

Roelemans commented Aug 11, 2020

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.

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:

  • getters of things like position, speed and current/torque etc. should be measured by any motor controller
  • actuation commands for torque/position
  • some kind of initialization. For the iMC this is split into initialize and goToOperationEnabled, which might not be necessary for all controllers. These can just have an empty method for one of them. I just realized that I did not have a close enough look at initialize and exactly what roll the EthercatMaster, will look into this tomorrow.
  • Perhaps some kind of shutdown (which can be implemented in the destroyer)

That leaves us with the methods

  • Every motor controller has some parameters we will want to monitor, hence the getStates method. This might require some additional work to get to work with multiple motor controllers, mainly because one can't subclass ros messages simply (see pr description)
  • Virtually every motor controller has multiple actuation modes (position/effort/velocity)
  • getSlaveIndex is relevant for any ethercat motorcontroller and will return -2/-1 for any other to indicate ethercat is unused
  • checkState is for responding to errors thrown by the motor controller

Does that explain your doubts?

Copy link
Contributor

@Olavhaasie Olavhaasie left a 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.

march_hardware/CMakeLists.txt Outdated Show resolved Hide resolved
virtual MotorControllerStates getStates() = 0;

virtual ActuationMode getActuationMode() const = 0;
virtual uint16_t getSlaveIndex() const = 0;
Copy link
Contributor

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.

Copy link
Contributor Author

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

Comment on lines 12 to 15
virtual double getAngleRadAbsolute() = 0;
virtual double getAngleRadIncremental() = 0;
virtual double getVelocityRadAbsolute() = 0;
virtual double getVelocityRadIncremental() = 0;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

@Roelemans Roelemans Aug 12, 2020

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.

march_hardware/include/march_hardware/joint.h Outdated Show resolved Hide resolved
return -1;
}
return this->imc_->getVelocityIUIncremental();
return this->controller_->getTorque();
Copy link
Contributor

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

Copy link
Contributor Author

@Roelemans Roelemans Aug 12, 2020

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

Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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/test/joint_test.cpp Show resolved Hide resolved
march_hardware_builder/src/hardware_builder.cpp Outdated Show resolved Hide resolved
@Roelemans Roelemans marked this pull request as draft August 14, 2020 10:02
@Roelemans Roelemans changed the title Feature/pm 524 add odrive option Feature/pm 524 Generalize motor controller Aug 14, 2020
Copy link
Contributor

@Olavhaasie Olavhaasie left a 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/src/ethercat/ethercat_master.cpp Outdated Show resolved Hide resolved
march_hardware/src/imotioncube/imotioncube.cpp Outdated Show resolved Hide resolved
Comment on lines 46 to 62
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;
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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();
Copy link
Contributor

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.

@Ishadijcks
Copy link
Contributor

@TimBuckers @martijnvandermarel Here's an interesting issue

@Roelemans Roelemans marked this pull request as ready for review August 24, 2020 20:05
@Roelemans Roelemans marked this pull request as draft August 24, 2020 20:05
Copy link
Contributor

@ThijsRay ThijsRay left a 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.

march_hardware/include/march_hardware/ethercat/slave.h Outdated Show resolved Hide resolved
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;
Copy link
Contributor

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?

Copy link
Contributor Author

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();
Copy link
Contributor

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?

Copy link
Contributor Author

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();
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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

march_hardware_builder/src/hardware_builder.cpp Outdated Show resolved Hide resolved
@@ -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,
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oopsie 😅

march_hardware_builder/src/hardware_builder.cpp Outdated Show resolved Hide resolved
}

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(
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
not-tested-on-march When a PR has not yet been tested on March
Development

Successfully merging this pull request may close these issues.

5 participants