Skip to content

Capability 002: Remote Control API - Translate

Status: Accepted

Context

The Superordinate Controller provides a REST API called the Remote Control API. With it, and with the Web App that consumes it, the Robot Operator drives the robot remotely. API and Web App are debugging tools for the Robot Operator. This capability introduces the API with "translation of the robot in any direction" and "stop" being the first operations.

Following the principle Safe when silent of the mission statement, the Web App is the originator of a Remote Drive Command: it repeats it at the cycle time of DriveCommand for as long as the robot is meant to keep moving. The Superordinate Controller turns each one into a Drive Command but does not repeat it. When the repetition stops, it falls back to the Neutral Drive Command of capability 001.

References

Details

Endpoints

Method Path Request body Success
POST /v1/drive-commands Remote Drive Command 202 Accepted
POST /v1/stop-commands none 202 Accepted
GET /healthz/live none 200 OK
GET /healthz/ready none 200 OK

Remote Drive Command

Field Type Range Meaning
direction integer 0–359 Direction of the translation in degrees: 0 forward, 90 right, 180 backward, 270 left, seen from above
speed integer 0–100 Speed of the translation in percent of the maximum speed

Kinematics

The motors are mounted at \(\theta_1 = 60^\circ\), \(\theta_2 = 180^\circ\), and \(\theta_3 = 300^\circ\). For a translation in direction \(\alpha\) at speed \(s\), motor \(i\) gets the target speed

\[v_i = \operatorname{round}\bigl(10 \cdot s \cdot \sin(\theta_i - \alpha)\bigr)\]

rounded half away from zero. \(|v_i| \le 10 \cdot s \le 1000\), so every result lies in the range of DriveCommand.

direction speed Motor1TargetSpeed Motor2TargetSpeed Motor3TargetSpeed
0 100 866 0 -866
90 100 -500 1000 -500
180 100 -866 0 866
270 100 500 -1000 500
0 50 433 0 -433
45 100 259 707 -966
any 0 0 0 0

Functional Requirements

  1. The Robot Operator sends a Remote Drive Command. The Superordinate Controller computes the Drive Command from it as given under Kinematics and sends it at once. It does not repeat it.
  2. A Remote Drive Command is active for 500 ms after its receipt, the Remote Drive Command Timeout. A newer Remote Drive Command replaces it and starts the timeout anew, whichever client sends it. As long as a Remote Drive Command is active, the Superordinate Controller suspends its own cycle. After the timeout, it sends the Neutral Drive Command at once and resumes its cycle.
  3. The Robot Operator sends a Stop Command. The Superordinate Controller ends the active Remote Drive Command and enters the Safe Mode: it sends the Neutral Drive Command at once and resumes its cycle. The next Remote Drive Command leaves the Safe Mode.
  4. /healthz/live reports whether the process runs. /healthz/ready reports whether the Superordinate Controller accepts commands: it is started, not shutting down, and the CAN Adapter is available.

Non-Functional Requirements

  1. The first CAN Frame Line of a Remote Drive Command or a Stop Command arrives on the serial line at most 100 ms after the request, with one client sending one request every 250 ms, measured by the receive time on a pseudo-terminal.
  2. When the Remote Drive Commands stop, the Neutral Drive Command arrives on the serial line 500 ms to 550 ms after the last one, measured by the receive time on a pseudo-terminal.
  3. A request that triggers an action, such as sending a CAN message, is answered with 202 Accepted once the request is valid. A request that only returns data is answered with 200 OK.

Technical Requirements

  1. The HTTP server is cpp-httplib, which already serves the health endpoints.
  2. The API is served over plain HTTP.
  3. The port is configurable through an environment variable, default 8081, so that a container platform can map it. Anyways, using environment variables for service configuration is a good idea, see The Twelve Factor App.
  4. The Web App is loaded from a developer notebook, not from the Superordinate Controller, so its requests are cross-origin. The allowed CORS origin is configurable through an environment variable.
  5. The API has no authentication.

Error Handling

  1. The Superordinate Controller validates every request; a request may be composed by hand, e.g. with cURL. Malformed JSON, a missing or unknown field, a wrong type, or a value out of range is a functional error and is answered with 400 Bad Request.
  2. If the CAN Adapter is unavailable, a command is answered with 503 Service Unavailable and /healthz/ready reports 503 as well.
  3. Every other error is a technical error and is answered with 500 Internal Server Error.
  4. The body of an error response is a problem detail of RFC 9457 (application/problem+json); it never reveals internals such as stack traces or device paths.
  5. Every error is logged once, where it is handled.
  6. A CORS_ALLOWED_ORIGIN that is no origin is logged, naming the variable, and the Superordinate Controller exits with a non-zero status before it serves. An origin consists of the scheme http or https, a host, and an optional port; it has no path and no trailing /.

Out of Scope

  • Rotation of the robot
  • Telemetry of the robot
  • Metrics and tracing, as demanded by the monitoring guideline for services
  • Authentication and TLS
  • Serving the Web App

Acceptance Criteria

All criteria except the last run against a pseudo-terminal.

  1. A Remote Drive Command of each row of the table under Kinematics: answered with 202 Accepted; the CAN Frame Line of the Drive Command with the target speeds of that row arrives as required by Non-Functional Requirement 1. Example: {"direction": 0, "speed": 100} yields Motor1TargetSpeed 866, Motor2TargetSpeed 0, Motor3TargetSpeed -866.
  2. One Remote Drive Command, then none: no further CAN Frame Line with its target speeds, and the Neutral Drive Command arrives as required by Non-Functional Requirement 2, followed by the own cycle as in capability 001.
  3. Remote Drive Commands every 250 ms for 3 s: only their Drive Commands arrive, no Neutral Drive Command in between.
  4. Two clients alternate Remote Drive Commands with different directions: each Drive Command follows the last request received.
  5. A Stop Command while a Remote Drive Command is active: answered with 202 Accepted; the Neutral Drive Command arrives as required by Non-Functional Requirement 1. A following Remote Drive Command yields its Drive Command again.
  6. Invalid Remote Drive Commands are answered with 400 Bad Request and an application/problem+json body, and yield no Drive Command of their own:
Request body Reason
{"direction": 0, "speed": 100 malformed JSON
{"speed": 100} missing field
{"direction": 0, "speed": 100, "rotation": 1} unknown field
{"direction": "north", "speed": 100} wrong type
{"direction": 360, "speed": 100} value out of range
{"direction": 0, "speed": 101} value out of range
{"direction": 0, "speed": -1} value out of range
  1. Started: /healthz/live and /healthz/ready answer 200 OK.
  2. The pseudo-terminal is closed: a Remote Drive Command is answered with 503 Service Unavailable and an application/problem+json body that names no device path; /healthz/ready answers 503 Service Unavailable, /healthz/live still 200 OK.
  3. Started with a port and a CORS origin from the environment: the API answers on that port, and a preflight request from that origin is allowed, one from another origin is not. Started with a CORS origin that is no origin, such as http://localhost:8080/, http://localhost:8080/app, localhost:8080, or *: exits with a non-zero status and a log message naming CORS_ALLOWED_ORIGIN.
  4. On the robot, first lifted with free wheels and then on the floor, at 20 % speed: direction 0 moves the robot forward, 90 to the right, 180 backward, 270 to the left; it stops within 1 s after the Web App stops sending and at once on a Stop Command (manual test test/manual/robot.md).

Decisions

  1. Plain HTTP, no TLS, despite the security guideline. The robot runs in a private network; certificates would only add effort to the project. Consequence: commands travel unencrypted; TLS needs a certificate and an OpenSSL dependency once it becomes necessary.
  2. A Remote Drive Command is sent at once and suspends the cycle of the Superordinate Controller while it is active. Waiting for the next cycle could take up to 250 ms and break the latency of 100 ms; a Neutral Drive Command in between would make the robot jerk. Consequence: while the Web App drives, it sets the interval between Drive Commands.
  3. The Remote Drive Command Timeout is 500 ms. It is twice the cycle time of DriveCommand, so one late request does not stop the robot, and below the 800 ms timeout of the Drive, so the Superordinate Controller stops the robot before the Drive has to. Consequence: a Web App that repeats at a longer interval than 500 ms cannot keep the robot moving.
  4. The last command wins, whichever client sends it. In practice there is one client; arbitration between clients would add complexity without benefit. Consequence: two clients sending at the same time override each other.