-
Notifications
You must be signed in to change notification settings - Fork 30
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
Add RosPositionCommandExecutor #173
Changes from 3 commits
8feb924
811a63b
dd96c10
83f69dd
cf8f104
9d9fb3d
648c2bf
a38c65a
06dad13
c6462db
e16880e
ce86f93
c90cd8d
2d3a5bc
c7c2040
ea130a6
eaff64c
d9d36fc
3af8671
2174930
6ecb30e
c3eb6f3
47a9852
bebb761
f1d805f
fc7cd1c
1eba53b
fa23b68
cc709a2
90bb1c9
db9bd59
4fc36d6
5a1059a
dc53d76
d3b1647
1bd2834
5e18e73
971bb6a
35d8144
571bc22
b89672b
7973137
45d632b
5e38013
83a41fd
e18ab71
0ecb2b9
cd516a2
061e676
7474bea
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 |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Assume it has 4 positions | ||
#ifndef AIKIDO_CONTROL_ROS_ROSBARRETTHANDPOSITIONCOMMANDEXECUTOR_HPP_ | ||
#define AIKIDO_CONTROL_ROS_ROSBARRETTHANDPOSITIONCOMMANDEXECUTOR_HPP_ | ||
#include <chrono> | ||
#include <future> | ||
#include <mutex> | ||
#include <ros/ros.h> | ||
#include <ros/callback_queue.h> | ||
#include <sensor_msgs/JointState.h> | ||
#include <pr_control_msgs/SetPositionAction.h> | ||
#include <actionlib/client/action_client.h> | ||
#include <Eigen/Dense> | ||
|
||
namespace aikido{ | ||
namespace control { | ||
namespace ros { | ||
|
||
/// This class sends commands for the hand position to the ROS server | ||
class RosBarrettHandPositionCommandExecutor | ||
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. Changed name and is now inheriting |
||
{ | ||
public: | ||
/// Constructor | ||
/// \param[in] node ROS node handle for action client. | ||
/// \param[in] serverName Name of the server to send traejctory to. | ||
/// \param[in] connectionTimeout Timeout for server connection. | ||
/// \param[in] connectionPollingPeriod Polling period for server connection. | ||
template <typename DurationA, typename DurationB> | ||
RosBarrettHandPositionCommandExecutor( | ||
::ros::NodeHandle node, | ||
const std::string& serverName, | ||
const DurationA& connectionTimeout = std::chrono::milliseconds{1000}, | ||
const DurationB& connectionPollingPeriod = std::chrono::milliseconds{20} | ||
); | ||
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. Nit: I don't think there is any value in templateing I believe @jslee02, @gilwoolee, and I discussed this in a previous PR. Tagging them to see if they have anything to add. 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. Removed template params and changed to |
||
|
||
~RosBarrettHandPositionCommandExecutor(); | ||
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. Either make this 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. Made |
||
|
||
/// Sends positions to ROS server for execution. | ||
/// \param[in] goalPositions Vector of 3 finger positions and 1 spread position | ||
/// \param[in] jointNames Vector of 4 joint names for fingers | ||
std::future<void> execute(Eigen::VectorXd& goalPositions,std::vector<std::string>& jointNames); | ||
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. Done both |
||
|
||
/// Sends positions to ROS server for execution. | ||
/// \param[in] goalPositions Vector of 3 finger positions and 1 spread position | ||
/// \param[in] jointNames Vector of 4 joint names for fingers | ||
/// \param[in] startTime Start time for the trajectory. | ||
std::future<void> execute( | ||
Eigen::VectorXd& goalPositions, | ||
std::vector<std::string>& jointNames, | ||
const ::ros::Time& startTime); | ||
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 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. Removed - only a single |
||
|
||
void step(); | ||
|
||
private: | ||
using HandPositionActionClient | ||
= actionlib::ActionClient<pr_control_msgs::SetPositionAction>; | ||
using GoalHandle = HandPositionActionClient::GoalHandle; | ||
|
||
bool waitForServer(); | ||
|
||
void transitionCallback(GoalHandle handle); | ||
|
||
::ros::NodeHandle mNode; | ||
::ros::CallbackQueue mCallbackQueue; | ||
HandPositionActionClient mClient; | ||
HandPositionActionClient::GoalHandle mGoalHandle; | ||
|
||
std::chrono::milliseconds mConnectionTimeout; | ||
std::chrono::milliseconds mConnectionPollingPeriod; | ||
|
||
bool mInProgress; | ||
std::promise<void> mPromise; | ||
Eigen::VectorXd mGoalPositions; | ||
|
||
std::mutex mMutex; | ||
}; | ||
|
||
} // namespace ros | ||
} // namespace control | ||
} // namespace aikido | ||
|
||
#endif // ifndef AIKIDO_CONTROL_ROS_ROSBARRETTHANDPOSITIONCOMMANDEXECUTOR_HPP_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -429,6 +429,36 @@ trajectory_msgs::JointTrajectory toRosJointTrajectory( | |
return jointTrajectory; | ||
} | ||
|
||
//============================================================================= | ||
sensor_msgs::JointState toJointState( | ||
const Eigen::VectorXd& goalPositions, const std::vector<std::string> jointNames) | ||
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. Nit: Take both arguments by |
||
{ | ||
if (goalPositions.size() != jointNames.size()) | ||
{ | ||
std::stringstream message; | ||
message << "The size of goalPositions must be the same as jointNames!" ; | ||
throw std::invalid_argument(message.str()); | ||
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. Nit: Include |
||
} | ||
|
||
sensor_msgs::JointState jointState; | ||
|
||
const auto numJoints = jointNames.size(); | ||
|
||
jointState.name.reserve(numJoints); | ||
jointState.position.reserve(numJoints); | ||
|
||
for (size_t i = 0; i < numJoints ; ++i) | ||
{ | ||
jointState.name.emplace_back(jointNames[i]); | ||
jointState.position.emplace_back(goalPositions[i]); | ||
} | ||
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. Nit: All of this code can be replaced by: sensor_msgs::JointState jointState;
jointState.name = std::move(jointNames); // assuming jointNames is taken by value
jointState.position.assign(goalPositions.data(), goalPositions.data() + goalPositions.size());
return jointState; |
||
|
||
return jointState; | ||
|
||
} | ||
|
||
|
||
|
||
} // namespace ros | ||
} // namespace control | ||
} // namespace aikido |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
#include <aikido/control/ros/RosBarrettHandPositionCommandExecutor.hpp> | ||
#include <aikido/control/ros/Conversions.hpp> | ||
|
||
namespace aikido { | ||
namespace control { | ||
namespace ros { | ||
|
||
using std::chrono::milliseconds; | ||
|
||
//============================================================================= | ||
template <typename DurationA, typename DurationB> | ||
RosBarrettHandPositionCommandExecutor::RosBarrettHandPositionCommandExecutor( | ||
::ros::NodeHandle node, | ||
const std::string& serverName, | ||
const DurationA& connectionTimeout, | ||
const DurationB& connectionPollingPeriod) | ||
: mNode(std::move(node)) | ||
, mCallbackQueue{} | ||
, mClient{mNode, serverName, &mCallbackQueue} | ||
, mConnectionTimeout{ | ||
std::chrono::duration_cast<milliseconds>(connectionTimeout)} | ||
, mConnectionPollingPeriod{ | ||
std::chrono::duration_cast<milliseconds>(connectionPollingPeriod)} | ||
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. See my comment above about this cast. I'd prefer to eliminate the template parameters entirely and force the user to perform this conversion, if necessary. If we do decide to keep the template parameters, then this implementation needs to be moved into a header file. By convention, it would go into a file named |
||
, mInProgress{false} | ||
{ | ||
} | ||
|
||
//============================================================================= | ||
RosBarrettHandPositionCommandExecutor::~RosBarrettHandPositionCommandExecutor() | ||
{ | ||
// Do nothing. | ||
// TODO: Should we wait for the current trajectory to finish executing? | ||
} | ||
|
||
//============================================================================= | ||
std::future<void> RosBarrettHandPositionCommandExecutor::execute( | ||
Eigen::VectorXd& goalPositions, | ||
std::vector<std::string>& jointNames) | ||
{ | ||
static const ::ros::Time invalidTime; | ||
return execute(goalPositions, jointNames, invalidTime); | ||
} | ||
|
||
//============================================================================= | ||
std::future<void> RosBarrettHandPositionCommandExecutor::execute( | ||
Eigen::VectorXd& goalPositions, | ||
std::vector<std::string>& jointNames, | ||
const ::ros::Time& startTime) | ||
{ | ||
if (goalPositions.size() != 4) | ||
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. Replace the hard-coded 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. Actually delegated the check to the |
||
{ | ||
std::stringstream message; | ||
message << "GoalPositions must have 4 elements, but [" | ||
<< goalPositions.size() << "] given."; | ||
throw std::invalid_argument(message.str()); | ||
} | ||
|
||
pr_control_msgs::SetPositionGoal goal; | ||
|
||
// Convert goal positions and joint names to jointstate | ||
goal.command = toJointState(goalPositions,jointNames); | ||
|
||
goal.command.header.stamp = startTime; | ||
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 think 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. Removed as we no longer need |
||
|
||
if (!waitForServer()) | ||
throw std::runtime_error("Unable to connect to action server."); | ||
|
||
{ | ||
std::lock_guard<std::mutex> lock(mMutex); | ||
DART_UNUSED(lock); // Suppress unused variable warning | ||
|
||
if (mInProgress) | ||
throw std::runtime_error("Another trajectory is in progress."); | ||
|
||
mPromise = std::promise<void>(); | ||
mInProgress = true; | ||
mGoalHandle = mClient.sendGoal(goal, | ||
boost::bind(&RosBarrettHandPositionCommandExecutor::transitionCallback, this, _1)); | ||
|
||
return mPromise.get_future(); | ||
} | ||
|
||
} | ||
|
||
//============================================================================= | ||
// TODO : This is directly reused from RosTrajectoryExecutor.cpp | ||
// Anyway to reuse this? (assuming we want this here) | ||
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 agree! Code duplication sucks. 😁 It should be straightforward to move this to a helper function: template <class ActionSpec, class TimeoutDuration, class PeriodDuration>
bool waitForActionServer(
actionlib::ActionClient<ActionSpec>& actionClient,
ros::CallbackQueue* callbackQueue,
TimeoutDuration timeoutDuration = std::chrono::seconds{ 1 },
PeriodDuration periodDuration = std::chronos::milliseconds{ 10 }
); 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've moved this to |
||
bool RosBarrettHandPositionCommandExecutor::waitForServer() | ||
{ | ||
using Clock = std::chrono::steady_clock; | ||
|
||
const auto startTime = Clock::now(); | ||
const auto endTime = startTime + mConnectionTimeout; | ||
auto currentTime = startTime + mConnectionPollingPeriod; | ||
|
||
while (currentTime < endTime) | ||
{ | ||
mCallbackQueue.callAvailable(); | ||
|
||
// TODO: Is this thread safe? | ||
if (mClient.isServerConnected()) | ||
return true; | ||
|
||
currentTime += mConnectionPollingPeriod; | ||
std::this_thread::sleep_until(currentTime); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
//============================================================================= | ||
void RosBarrettHandPositionCommandExecutor::transitionCallback(GoalHandle handle) | ||
{ | ||
|
||
using actionlib::TerminalState; | ||
using Result = pr_control_msgs::SetPositionResult; | ||
|
||
if (handle.getCommState() == actionlib::CommState::DONE) | ||
{ | ||
std::stringstream message; | ||
bool isSuccessful = true; | ||
|
||
const auto terminalState = handle.getTerminalState(); | ||
if (terminalState != TerminalState::SUCCEEDED) | ||
{ | ||
message << "Action " << terminalState.toString(); | ||
|
||
const auto terminalMessage = terminalState.getText(); | ||
if (!terminalMessage.empty()) | ||
message << " (" << terminalMessage << ")"; | ||
|
||
mPromise.set_exception(std::make_exception_ptr( | ||
std::runtime_error(message.str()))); | ||
|
||
isSuccessful = false; | ||
} | ||
else{ | ||
message << "Execution failed."; | ||
} | ||
|
||
const auto result = handle.getResult(); | ||
if (result && result->success == false) | ||
{ | ||
if (!result->message.empty()) | ||
message << " (" << result->message << ")"; | ||
|
||
mPromise.set_exception(std::make_exception_ptr( | ||
std::runtime_error(message.str()))); | ||
|
||
isSuccessful = false; | ||
} | ||
|
||
if (isSuccessful) | ||
mPromise.set_value(); | ||
|
||
mInProgress = false; | ||
|
||
} | ||
} | ||
|
||
//============================================================================= | ||
// TODO : This is also basically copied from RosTrajectoryExecutor | ||
void RosBarrettHandPositionCommandExecutor::step() | ||
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 think that's fine in for now. We may want to write a base class for |
||
{ | ||
std::lock_guard<std::mutex> lock(mMutex); | ||
DART_UNUSED(lock); // Suppress unused variable warning. | ||
|
||
mCallbackQueue.callAvailable(); | ||
|
||
if (!::ros::ok() && mInProgress) | ||
{ | ||
mPromise.set_exception( | ||
std::make_exception_ptr(std::runtime_error("Detected ROS shutdown."))); | ||
mInProgress = false; | ||
} | ||
} | ||
|
||
|
||
} // namespace ros | ||
} // namespace control | ||
} // namespace aikido |
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.
Nit: This is not specific to finger joints! 😉
Also, we should add "position" to the name of this function, since we may very well need equivalent functions for velocities and/or efforts. Perhaps
positionsToJointState
?