Skip to content
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

Merged
merged 13 commits into from
Jan 24, 2022
Merged

Conversation

Yadunund
Copy link
Member

@Yadunund Yadunund commented Jan 15, 2022

This PR aims to introduce support for PerformAction event. Changes

  1. JSON schema for PerformAction event description
  2. FleetUpdateHandle::add_performable_action(const std::string& category) API: Set the category of actions that robots in this fleet can perform.
  3. 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 and RobotUpdateHandle::update_action_remaining_time(rmf_traffic::Duration duration) to update the estimate of the time remaining for the action.
  4. Standby and Active definitions for PerformAction

This PR requires open-rmf/rmf_task#49

Signed-off-by: Yadunund <yadunund@openrobotics.org>
Signed-off-by: Yadunund <yadunund@openrobotics.org>
@Yadunund Yadunund requested a review from mxgrey January 15, 2022 23:16
using ActionExecutor =
std::function<void(
const nlohmann::json& action,
const ActionCompleted& completed)
Copy link
Contributor

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

@mxgrey mxgrey Jan 17, 2022

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.

Copy link
Member Author

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

  1. What is the purpose of the any, detail and about fields?
  2. I assume the intention is for the entire description JSON block to be passed into PerformAction::Description::make() as the action. Is this correct?

Copy link
Contributor

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

@mxgrey mxgrey left a 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(
Copy link
Contributor

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.

Copy link
Member Author

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(~)

Copy link
Member Author

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(~)

Copy link
Contributor

@mxgrey mxgrey Jan 24, 2022

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.

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

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

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

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

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

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

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.

@mxgrey
Copy link
Contributor

mxgrey commented Jan 24, 2022

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>
@Yadunund
Copy link
Member Author

Thanks for all the great feedback!

Signed-off-by: Yadunund <yadunund@openrobotics.org>
Copy link
Contributor

@mxgrey mxgrey left a comment

Choose a reason for hiding this comment

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

Looks great! 🎉

@Yadunund Yadunund merged commit 10c9a51 into redesign_v2 Jan 24, 2022
@Yadunund Yadunund deleted the perform_action branch January 24, 2022 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants