Protocol Definition
The application protocol runs over a persistent socket connection through the TCP/IP stack. Since it relies on TCP for several of its core behaviors, it's recommended, but not required, to disable Nagle's Algorithm by enabling the TCP_NODELAY socket option; and to enable the TCP keepalive with an aggressive setting. By default, the protocol uses port TCP:54817
.
Architecture Components
The protocol contemplates three types of nodes: client, server and mcu.
- Client: Refers to the remote user, which can control a mcu.
- Server: Refers to the orchestrator service, in charge of query validation and routing client queries to the indicated MCU.
- MCU: Refers to the remote robotic system, which may be remotely operated by a client.
Client Role
The client is the only proactive node in the architecture: it sends commands to the server, which either relays them to the mcu or handles them directly before replying with a control response.
Server Role
The server acts as the orchestrator in this architecture. Its functions are routing and validation, interconnecting each client with the mcu it intends to control, and validating the parameters sent by the client before relaying them to the mcu for execution. The server will never send any message to the client or mcu unless prompted by a client query.
MCU Role
The mcu is the passive executor node, responsible solely for carrying out orders received from the server. On startup, it sends a login message to the server, then waits for incoming commands, executes them, and replies with an ACK control message.
MCU Types
The protocol contemplates two types of MCU robotic systems:
- SmartMCU: Refers to a mcu that is capable of generating its own PWM signal values for a given position. This means that a SmartMCU must be able to store locally the PWM calibration values, it also needs to be capable of storing pending movements, and will be required to, on connection, send the current position of each of its servos.
- DumbMCU: Refers to a mcu that executes movements by reading the PWM signal value directly from the query. This means that a DumbMCU will not receive any information until a movement is expected to be executed, at which point, it will receive a query with the PWM signal value for each servo, instead of a servo position (0º-179º). On connection, a DumbMCU is required to send the number of servos it has.
No binary zero convention
In order to avoid potential issues with the zero-value bytes (uint8_t b=0
), every numerical field that could possibly take the value zero gets offset by +1. This means that fields such as servoId
(range 0-31) and servoPosition
(range 0-179) will be sent with a +1 positive offset applied: servoId
would take the range (1-32) and servoPosition
the range (1-180).
Protocol Query Format
The protocol distinguishes two types of query formats: A client - server format, and a server - mcu format. These two formats were defined to improve the clarity of the protocol, making it obvious which part of the communication chain a given query belongs to. However, the login query poses an exception to these formats, where client and mcu have a unique query style, also different from the other query formats.
The protocol query uses plain, raw binary, with no codification or encryption. All format and structure characters are standard ASCII.
Client - Server Query Format
The message follows a set structure: it always starts with a constant header, !s-
, followed by a 4-byte function selector code CODE
, and is terminated by a constant tail -e!
. This structure can accommodate additional information, for those functions that require it: the information will be added behind the four-byte function selector code (CODE
), separated with an additional slash -
.
This means that the minimum query length will be 11
bytes (3 from the header !s-
, +4 from the selection code CODE
, and +3 from the tail sequence -e!
). Any query with less than 11 bytes
can be safely deleted, as it would mean that some kind of error happened during query formation.
Server - MCU Query Format
Similarly to the client - server query, the server - mcu query also uses an structured format with header and tail. However, in this second format, the header is reduced to a single -
, while the tail sequence takes the form -!
. The 4-byte function selector code becomes a single-byte code c
, and the additional information, when needed, is added in the exact same format as the client - server format: behind the single-byte code c
, and separated with a dash character -
.
PWM Signal Encoding
Some queries, such as Upload Calibration Data, Servo Movement, and Servo Movement (Delayed Mode), rely on a custom uint16_t
encoding to transmit the PWM signal.
This codification is explained in the documentation for each relevant query. It's also available on the following area:
PWM Signal codification
Since the PWM signal can take various bit resolution levels, usually ranging from 8-bit to 16-bit, with >16bit being niche or application-specific territory, the procotol allocates 2 bytes (up to 15-bit pwm resolution) for PWM signals on SRVP queries.
This means that in order to follow the protocol no-zero convention, each individual byte needs to incorporate this offset. This is required because, for values of PWM_SIGNAL<255
, the upper-byte will always be zero, and for the specific value PWM_SIGNAL=256
, the lower-byte will be zero.
How is offset applied
The offset is introduced by fixing the most significant bit of the uint16_t PWM_SIGNAL
to 1
, and then adding 1 to the normal PWM signal value. This results in a 15-bit signal with one fewer representable PWM value, a range of [0 → 32766]. On PWM servos that use the full 15-bit resolution over a 180º linear range, this results in a precision loss of approximately 5.493 millidegrees reduction in total range, and 0.1676 microdegrees decrease in angular resolution per step, which is effectively negligible for most use-cases.
To introduce this offset, the formula PWM_SIGNAL = pwm + (1+(0b1<<15))
is used; and the formula pwm = PWM_SIGNAL - (1+(0b1<<15))
is used on the receiving end to undo the offset applied.
Endianness
To avoid the endianness problem, the protocol introduces a normalized implementation through which uint16_t
values are split into two uint8_t
values in a set order (the first byte will be the upper-byte, followed by the lower-byte). The visual representation of this transformation would be:
And the functions used for this are:
divideIntoBytes() | |
---|---|
restore16int() | |
---|---|
Protocol Queries Overview
The following table lists all queries defined by the protocol, as well as a brief description and links to each of their documentation pages:
Query | Additional Information | Description | Documentation |
---|---|---|---|
!s-_ACK-[c]-e! |
Success code | Sent by server or MCU to acknowledge successful execution | Link |
!s-NACK-[c]-e! |
Error code | Sent by server or MCU to indicate an error | Link |
!s-Client_here-e! |
None | Sent by client to identify and initiate login | Link |
!s-NodeMCU_here-[info]-e! |
Unique ID + Servo positions and count | Sent by MCU to register itself with the server | Link |
!s-sMCU-[info]-e! |
MCU Unique ID | Sent by client to select which MCU to control | Link |
!s-SRVP-[info]-e! |
Number of movements + Information for each servo movement | Sent by client to execute servo movement on selected MCU | Link |
-m-[info]-! |
Number of movements + Information for each servo movement | Sent by server to MCU to execute servo movements | Link |
-u-[info]-! |
Number of movements + Information for each servo movement | Sent by server to MCU to store delayed servo movements | Link |
!s-iMCU-[info]-e! |
On server response: Servo positions and count | Sent by client to request current MCU servo positions | Link |
!s-eMOD-[info]-e! |
The execution mode | Sent by client to set MCU execution mode (real-time or delayed) | Link |
!s-mALL-e! |
None | Sent by client to trigger execution of stored servo movements | Link |
-e-! |
None | Sent by server to MCU for execution of stored movements | Link |
!s-uINF-[info]-e! |
Number of servos + calibration data for each servo | Sent by client to upload MCU calibration (PWM) data to Server | Link |
!s-sOFF-e! |
None | Sent by client to shut down the server remotely | Link |