-
Notifications
You must be signed in to change notification settings - Fork 64
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
Support PerformAction event in composed tasks #155
Conversation
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
using ActionExecutor = | ||
std::function<void( | ||
const nlohmann::json& action, | ||
const ActionCompleted& completed) |
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.
Let's send this argument by value instead of by reference as a kind of encouragement for the user to save a copy of it. There's nothing wrong with passing by reference, but it usually carries the implication that the object is only meant to be viewed rather than used.
try | ||
{ | ||
const nlohmann::json action = | ||
nlohmann::json::parse(msg["action"].get<std::string>()); |
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 don't think this is a good way to use the nlohmann
library or JSON more generally. What this is literally saying is that the action
field will refer to a string, and that string will contain an entire JSON message inside of it. So that might look something like this:
"action": "{
\"clean\": {
\"zone\": \"terminal_3\"
}
}"
It would become a nightmare of escaping quotes and a very confusing structure.
I recommend the following schema instead:
"perform_action": {
"category": "some_simple_string",
"description": {
"any": 0
"details": { },
"about": [ ],
"this": true
"action": " "
},
"expected_finish_location": <optional>,
"unix_millis_action_duration_estimate": <optional>,
... any other optional task planner details ...
}
The add_performable_action
function will have this signature instead:
FleetUpdateHandle& FleetUpdateHandle::add_performable_action(
const std::string& category,
ConsiderRequest consider);
We will maintain a map from category
string keys to consider
callbacks. When a perform_action
event comes in, we will do a look up on that map. If the lookup fails, we reject the task. If the look up succeeds, we trigger the consider
callback and then accept or reject based on its response.
The ActionExecutor
signature would look like this instead:
using ActionExecutor =
std::function<void(
const std::string& category,
const nlohmann::json& description,
ActionCompleted completed)
This should make it very easy for users to put in their own arbitrary JSON details for the action while still allowing the fleet adapter to do the heavy lifting in weeding out irrelevant task requests.
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.
Thanks for the feedback. I can see how this is much easier for the user. I have a couple of questions on the schema
- What is the purpose of the
any
,detail
andabout
fields? - I assume the intention is for the entire
description
JSON block to be passed intoPerformAction::Description::make()
as theaction
. Is this correct?
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 any
, details
, about
, this
, action
are just examples of how a user can fill the description
JSON with any kind of data they want. You can totally disregard them, they're not actually part of the schema, just placeholders.
I assume the intention is for the entire description JSON block to be passed into PerformAction::Description::make() as the action. Is this correct?
That's right, the PerformAction::Description
would need to have some kind of action_message
field (or something similarly named) to store the message that gets passed to the robot command handle.
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
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 generally looking really good! I just have some recommendations for tightening up the API.
} | ||
}; | ||
|
||
_pimpl->deserialization.event->add( |
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.
We shouldn't need to re-add "perform_action"
each time the user adds a new category. We can add this once at startup and then just update the action category map when the user wants to add a new action category.
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.
Moved it into FleetUpdateHandle::Implementation::make(~)
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.
Moved it into FleetUpdateHandle::Implementation::make(~)
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.
Sorry to be nitpicky about this, but could we imitate the pattern that's being used in FleetUpdateHandle::Implementation::add_standard_tasks
? Basically put this implementation into a events::add_perform_action(~)
function and define that function in src/rmf_fleet_adapter/events/PerformAction.cpp
?
I think that would organize the code a bit better and reduce the already considerable amount of noise in this make
function.
rmf_fleet_adapter/include/rmf_fleet_adapter/agv/RobotUpdateHandle.hpp
Outdated
Show resolved
Hide resolved
rmf_fleet_adapter/include/rmf_fleet_adapter/agv/RobotUpdateHandle.hpp
Outdated
Show resolved
Hide resolved
rmf_fleet_adapter/src/rmf_fleet_adapter/events/PerformAction.cpp
Outdated
Show resolved
Hide resolved
rmf_fleet_adapter/src/rmf_fleet_adapter/events/PerformAction.cpp
Outdated
Show resolved
Hide resolved
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
// TODO: Consider giving access to the participant schedule and | ||
// traffic negotiation | ||
|
||
~ActionExecution() |
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.
We should put the definition of this function into the proper translation unit, i.e. RobotUpdateHandle.cpp
. I understand you were probably copying the example code that I provided, but I defined it inline for brevity. It's not actually good to leave this function definition exposed in a public API, because that can lead to poorly defined behavior if we ever change the implementation of the function.
}, | ||
"description": { | ||
"description": "A dictionary describing the action to be performed.", | ||
"type": "object" |
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.
Let's not force this to be a dictionary. Maybe a system integrator would like this to be an array. We can just remove this "type": "object"
specification and leave it ambiguous.
I'd also recommend changing description
to something like `"Additional information that will be passed along to the action executor."
void RobotContext::action_executor( | ||
RobotUpdateHandle::ActionExecutor action_executor) | ||
{ | ||
if (action_executor != 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.
I'm not actually sure that we should block this. What if the user is trying to erase its action executor for some reason?
I recommend instead of blocking, we simply emit a warning that the action executor is being nullified which will lead to an automatic critical task error if a task requires an action to be performed while this is still set to null.
{ | ||
return *handle._pimpl; | ||
} | ||
|
||
static const Implementation& get(const RobotUpdateHandle& handle) | ||
static const Implementation &get(const RobotUpdateHandle &handle) |
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.
Looks like some automatic styling has messed things up here.
_state->update_status(Status::Error); | ||
const std::string msg = "ActionExecutor not set via RobotUpdateHandle. " | ||
"Unable to perform the requested action."; | ||
_state->update_log().info(msg); |
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 should be an error-level log, not info.
const std::string msg = "ActionExecutor not set via RobotUpdateHandle. " | ||
"Unable to perform the requested action."; | ||
_state->update_log().info(msg); | ||
_finished(); |
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.
Let's update the status to an error before finishing.
This is shaping up very nicely. I just left a few more small remarks to address and then I'll be happy to approve 👌 |
Signed-off-by: Yadunund <yadunund@openrobotics.org>
Thanks for all the great feedback! |
Signed-off-by: Yadunund <yadunund@openrobotics.org>
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.
Looks great! 🎉
This PR aims to introduce support for
PerformAction
event. ChangesPerformAction
event descriptionFleetUpdateHandle::add_performable_action(const std::string& category) API: Set the category of actions that robots in this fleet can perform.
RobotUpdateHandle::set_action_executor(ActionExecutor action_executor)
API: Set the executor which can be used by the fleet adapter to request a robot to perform an action andRobotUpdateHandle::update_action_remaining_time(rmf_traffic::Duration duration)
to update the estimate of the time remaining for the action.Standby
andActive
definitions forPerformAction
This PR requires open-rmf/rmf_task#49