Capability 005: Telemetry¶
Status: Accepted
Context¶
The Robot Operator drives the robot with the Web App but does not see how it responds: whether
the motors turn as commanded and how much charge the battery has left. The Drive reports both on
the CAN bus, the Superordinate Controller receives them as > lines of the CAN Adapter since
capability 004.
With this capability, the Superordinate Controller keeps the latest value of each quantity and pushes a Telemetry Snapshot to every connected client at a fixed interval. A client sees the current state, not every message: the Drive sends 300 MotorNSpeed messages per second, a human reads at most a few values per second.
References¶
- Interface description:
remote_control_api.json - API design: API Design Guidelines,
directives in
api_design.md - CAN messages MotorNStatus and MotorNSpeed:
drive.dbc - Mapping of the boards to the motors: Motor Controller Bridge, capabilities 003 and 004
>lines of the CAN Adapter:can_adapter.md- CORS, error handling, and environment variables: capability 002
- Server-Sent Events: HTML Living Standard, section 9.2
Details¶
Endpoint¶
| Method | Path | Request body | Success |
|---|---|---|---|
| GET | /v1/telemetry |
none | 200 OK, text/event-stream |
Telemetry Stream¶
The response is an endless stream of Server-Sent Events. Each event has the event type
telemetry and one Telemetry Snapshot as JSON in its data:
event: telemetry
data: {"type":"telemetry","version":1,"timestamp":"2026-10-09T12:00:00.000Z","source":"superordinate-controller","batteryVoltage":36.25,"motorSpeeds":[50,50,100]}
| Field | Type | Meaning |
|---|---|---|
type |
string | Always telemetry |
version |
integer | Version of the snapshot format, 1 |
timestamp |
string | Time of the snapshot, ISO 8601 in UTC with milliseconds |
source |
string | Always superordinate-controller |
batteryVoltage |
number or null |
Battery Voltage in volts, two decimals; null while it is not current |
motorSpeeds |
array of 3 | Motor Speed of motors 1 to 3 in percent, integer from -100 to 100; an entry is null while it is not current |
A value is current while its last CAN message is at most 3 s old.
Received CAN Messages¶
| CAN message | > line identifier |
Taken for |
|---|---|---|
| Motor1Status | 0C210003 |
Battery Voltage of board 1 |
| Motor2Status | 0C220003 |
Nothing: it carries the same values as Motor1Status |
| Motor3Status | 0C230003 |
Battery Voltage of board 2 |
| Motor1Speed | 0C210004 |
Motor Speed of motor 1 |
| Motor2Speed | 0C220004 |
Motor Speed of motor 2 |
| Motor3Speed | 0C230004 |
Motor Speed of motor 3 |
Battery Voltage¶
The Battery Voltage is the mean of the Supply Voltages of board 1 and board 2 that are current.
If only one is current, it is that one; if none is, it is null.
Motor Speed¶
For motor \(i\) with the Measured Speed \(n_i\) and the reference \(n_{max}\) of
MOTOR_SPEED_AT_FULL_SCALE, the Motor Speed in percent is
rounded half away from zero, with \(\sigma_1 = \sigma_3 = 1\) and \(\sigma_2 = -1\). The sign of \(p_i\) is the sign of the target speed that turns motor \(i\) in this direction.
Test vectors with \(n_{max} = 1000\):
| Motor | > line |
\(p_i\) |
|---|---|---|
| 1 | >0C210004:F4 01 (500) |
50 |
| 2 | >0C220004:0C FE (-500) |
50 |
| 3 | >0C230004:4C 04 (1100) |
100 |
| 1 | >0C210004:05 00 (5) |
1 |
| 1 | >0C210004:FC FF (-4) |
0 |
Environment Variables¶
| Variable | Meaning | Default | Valid values |
|---|---|---|---|
MOTOR_SPEED_AT_FULL_SCALE |
Measured Speed that corresponds to 100 % | 1000 |
Integer from 1 to 32767 |
TELEMETRY_INTERVAL |
Milliseconds between two Telemetry Snapshots | 1000 |
Integer from 100 to 10000 |
Functional Requirements¶
- The Superordinate Controller takes the CAN messages of the table under Received CAN Messages
from the
>lines of the CAN Adapter and keeps the latest value of each, with its time of receipt. - A client opens the Telemetry Stream with
GET /v1/telemetry. The Superordinate Controller sends a Telemetry Snapshot at once and then everyTELEMETRY_INTERVALmilliseconds, until the client closes the connection. - Each Telemetry Snapshot carries the values as computed under Battery Voltage and Motor Speed at the time of the snapshot.
- Several clients may hold a Telemetry Stream at once, at most 4.
Non-Functional Requirements¶
- The first Telemetry Snapshot arrives at most 100 ms after the request.
- Two consecutive Telemetry Snapshots are
TELEMETRY_INTERVAL± 100 ms apart. - A value received on the pseudo-terminal appears in the next Telemetry Snapshot but one at the latest.
- With 4 open Telemetry Streams and 300 MotorNSpeed and 3 MotorNStatus messages per second on the pseudo-terminal, Non-Functional Requirement 1 of capability 002 still holds.
Technical Requirements¶
- The Telemetry Stream is served by
cpp-httpliblike the other endpoints, with the CORS handling of capability 002.
Error Handling¶
- A fifth request for a Telemetry Stream is answered with 503 Service Unavailable and an
application/problem+jsonbody, as for capability 002, Error Handling 4. - A
>line with an identifier of the table under Received CAN Messages whose payload has fewer than the bytes of its message is logged like an error line of capability 004, at most once per second, and does not change the kept value. - A
>line with any other identifier is ignored. - A client that closes its Telemetry Stream ends it without an error log.
- An invalid value of
MOTOR_SPEED_AT_FULL_SCALEorTELEMETRY_INTERVALis logged, naming the variable, and the Superordinate Controller exits with a non-zero status before it serves, as for capability 004, Error Handling 2.
Out of Scope¶
- Other values of the Drive: Controller Temperature, MotorNState, and the right output of board 2
- Minimum, maximum, or mean of a value over the interval
- Pushing an event at once when a value changes or an error occurs
- Telemetry of other subsystems, such as battery management and distance sensors
- Showing the telemetry in the Web App
Acceptance Criteria¶
All criteria except the last run against a simulated CAN Adapter on a pseudo-terminal.
- The simulated adapter sends
>0C210003:10 0E 1E 00(36.00 V) and>0C230003:42 0E 1F 00(36.50 V), and the>lines of the first three test vectors under Motor Speed: a client opens the Telemetry Stream and receives, within 100 ms, a Telemetry Snapshot withbatteryVoltage36.25 andmotorSpeeds[50, 50, 100], the response allowed for the CORS origin of capability 002. - Each test vector under Motor Speed yields its \(p_i\) in the next Telemetry Snapshot but one.
- Started with
TELEMETRY_INTERVAL=200: the Telemetry Snapshots arrive 200 ± 100 ms apart. - Motor1Status keeps arriving, Motor3Status arrived last 4 s ago:
batteryVoltageis that of Motor1Status. No message for 4 s:batteryVoltageand every entry ofmotorSpeedsarenull. - Started with
MOTOR_SPEED_AT_FULL_SCALE=500:>0C210004:F4 01(500) yields 100 for motor 1. - A
>line>0C210004:F4with too short a payload: logged, andmotorSpeedskeeps the previous entry of motor 1. - 4 clients hold a Telemetry Stream: a fifth is answered with 503 and an
application/problem+jsonbody; after one client closes its stream, a new one is served. - 4 open Telemetry Streams and the load of Non-Functional Requirement 4: Remote Drive Commands every 250 ms for 3 s meet Non-Functional Requirement 1 of capability 002.
- Started with an invalid value, e.g.
MOTOR_SPEED_AT_FULL_SCALE=0,=abc, orTELEMETRY_INTERVAL=50: exits with a non-zero status and a log message naming the variable. - On the robot, lifted with free wheels: a rotation at speed 50 shows about -50 % on all three
motors; a translation in direction 90 at speed 50 shows about -25 %, 50 %, and -25 %; the
Battery Voltage matches a multimeter on the battery within 0.5 V (manual test
test/manual/robot.md).
Decisions¶
- The Superordinate Controller pushes the latest state at a fixed interval, by Server-Sent
Events.
A client needs the current state, not every message; Server-Sent Events push in one direction
over plain HTTP with the CORS of capability 002, and a browser reconnects by itself. The fixed
interval also tells the client that the stream is alive.
Consequence: a short peak between two snapshots is not shown; each open stream holds a worker
thread of
cpp-httplib, hence the limit of 4. - The Superordinate Controller converts to volts and percent and flips the sign of motor 2. It knows the Drive: the boards report the Measured Speed of the right output with the opposite sign of its command, and the Motor Controller Bridge passes values on unchanged. A client only shows the values. Consequence: the sign of motor 2 rests on the firmware of the boards; the manual test on the robot verifies it.
- A value older than 3 s is
null, not its last value. An old value shown as current would hide a silent board; 3 s are three missed MotorNStatus messages. One limit for all values keeps the rule simple. Consequence: a Motor Speed stays shown for up to 3 s after its board falls silent. - The snapshot carries the message envelope without
correlationId. A snapshot answers no request and follows from no other message, so there is nothing to correlate. Consequence: this deviates from the envelope of the API design guideline.