Commands makes it simple to publish to ROS topics, call services or send goals to action servers on your robots over the internet through a REST API.

How it works

  1. Clients call the POST /robots/:id/commands endpoint on the Airbotics API. In the request body they provide the ROS interface (topic, service or action), the type of interface (e.g. std_msgs/msg/String), its name (e.g. /chatter), and a payload in JSON format.

  2. The command will then be created in the database, in a created state.

  3. If the robot is online the command will be immediately sent to the Airbotics agent on the robot and the state changed to sent. If the robot is not online the command will not be sent, moved into an error state, and an error will be returned to the client.

  4. If the command is received by the agent, it will be converted from JSON formant to ROS format and executed (i.e. published to a topic, service called, or goal sent to an action server).

  5. If the agent is instructed to call a service or send a goal to an action server that aren’t ready, the agent will report an error to the backend and the command will be moved into an error state. However, if the command was successfully executed, the agent will report this to the cloud and move the comannd to an executed state.

  6. The client can query state of a command with the GET /commands/:id endpoint.

Publishing to a topic

The request body to publish a message to a ROS topic looks like this:

{
  "interface": "topic",
  "name": "/chatter",
  "type": "std_msgs/msg/String",
  "payload": { 
    "data": "Hello world"
  }
}

Below is an explanation of each part:

ParameterDescriptionExample
interfaceThe ROS interface, this must be topic.topic
nameThe name of the topic./chatter
typeThe type of ROS interface the topic expects.std_msgs/msg/String
payloadThe valid ROS interface payload in JSON format .{ "data": "Hello world" }

Airbotics currently supports all message types defined in ROS common_interfaces. It is also possible to send custom messages that may be specific to your application.

See JSON to ROS conversion for more information about how to provide the correct payload for each ROS message type.

Calling a service

The request body to call a ROS service looks like this:

{
  "interface": "service",
  "name": "/add_two_ints",
  "type": "example_interfaces/srv/AddTwoInts",
  "payload": {
    "a": 1, 
    "b": 3
  }
}
ParameterDescriptionExample
interfaceThe ROS interface, this must be service.service
nameThe name of the service to call./add_two_ints
typeThe type of ROS interface that the service expects.example_interfaces/srv/AddTwoInts
payloadThe valid ROS interface payload in JSON format.{ "a": 2, "b": 3 }

If the service is not available when the agent receives the instruction the agent will report this to the backend and the command will enter an error state.

Airbotics considers the command to be executed successfully if the service has been called, even if the logic in your service failed to perform its function. The agent discards the response of the service.

Sending a goal to an action

The request body to send a goal to a ROS action looks like this:

{
  "interface": "action_send_goal",
  "name": "/turtle1/rotate_absolute",
  "type": "turtlesim/action/RotateAbsolute",
  "payload": {
    "theta": 90.0,
  }
}
ParameterDescriptionExample
interfaceThe ROS interface, this must be action_send_goal.action_send_goal
nameThe name of the action to call./turtle1/rotate_absolute
typeThe type of ROS interface that the service expects.turtlesim/action/RotateAbsolute
payloadThe valid ROS interface payload in JSON format.{ "theta": 90.0}

If the action server is not ready to accept a goal the agent will report this to the backend and the command will enter an error state.

Airbotics considers the command to be executed successfully if the goal has been successfully sent to the action server, even if the logic in your action failed to perform its function. The agent discards any feedback or result from the action server.

JSON to ROS conversion

When you send an API request to execute a command, you must provide data specific to ROS in JSON format. For the agent to be able to make the conversion from JSON to ROS, you must provide valid and corresponding values for type and payload, discussed below:

Providing a valid type

The type parameter must match the ROS convention for defining interfaces. Examples:

  • std_msgs/msg/String
  • std_srvs/srv/Empty
  • custom_msgs/msg/MyMessage
  • turtlesim/action/RotateAbsolute

If the agent cannot find the type an error will be produced, the agent will report this to the backend and move the command to an error state.

Providing a valid payload

A valid value for payload will depend on the value you provided for type. In general, you should supply the same arguments and types that ROS uses. This applies to messages, services and actions. You can inspect the fields and types of an interface using the following command:

ros2 interface show <interface_type>

If the agent cannot convert JSON to ROS format an error will be produced, the agent will report this to the backend and move the command to an error state.

See below for examples on how ROS types can be converted to JSON:

Example 1

We can examine the String message type in ROS with:

ros2 interface show std_msgs/msg/String

# Returns
String
  string data

We can see it has a single child property data with a type string. The JSON format for this is therefore:

"type": "std_msgs/msg/String"
---
"payload": {
  "data": "hello world"
}

Just like the the ROS message, the JSON has a single child with the same name (data) and a value that matches the expected type (string).

Example 2

Looking at a slightly more complex example:

ros2 interface show geometry_msgs/msg/Twist

# Returns
Twist
  Vector3 linear
    float64 x
    float64 y
    float64 z
  Vector3 angular
    float64 x
    float64 y
    float64 z

This time there are several child properties (linear and angular) and those children have children of their own (x, y and z). To convert this to JSON we follow the same convention as before:

"type": "geometry_msgs/msg/Twist"
---
"payload": {
  "linear": { 
    "x": 1.0, 
    "y": 1.0, 
    "z": 1.0 
  }, 
  "angular": { 
    "x" :1.0,
    "y": 1.0,
    "z": 1.0 
  }
}

Example 3

Finally, we take an even more complex example:

ros2 interface show std_msgs/msg/Float64MultiArray

# Returns
Float64MultiArray
  MultiArrayLayout layout
    MultiArrayDimension[] dim
      string label
      uint32 size
      uint32 stride
    uint32 data_offset
  float64[] data

Here there are several child properties whose children have children and where some of the children are arrays. But again, following the same convention, it can be converted to JSON like so:

"type": "std_msgs/msg/Float64MultiArray"
---
"payload": {
  "layout": {
    "dim": [
      {
        "label": "sensor-dim",
        "size": 3,
        "stride": 1
      }
    ],
    "data_offset": 0
  },
  "data": [
    1.0,
    2.0,
    3.0
  ]
}

States

A command can be in one of the following states:

StateDescription
createdThe request to send a command has been received by the backend but has not yet been attempted to be sent.
sentThe command has been attempted to be sent to the robot and should be received by the agent.
errorThe command has failed, e.g. because the robot is not online.
executedThe command has been received by the agent and has been executed.

Error codes

Commands can fail for various reasons, which are outlined below:

Error codeDescription
robot_not_onlineThe robot is not online.
invalid_typeThe command type cannot be found or is invalid.
invalid_payloadThe command payload does not match the expected payload.
service_not_readyThe service is not ready.
action_server_not_readyThe action server is not ready.
unknown_errorSomething has gone wrong with the agent.

FAQ