Skip to content

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 -.

!s-CODE-e!
!s-CODE-[ADDITIONAL_INFORMATION]-e!  

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 -.

-c-!
-c-[ADDITIONAL_INFORMATION]-!

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:

uint16 uint16

And the functions used for this are:

divideIntoBytes()
1
2
3
4
5
6
std::string QueryGenerator::divideIntoBytes(uint16_t u){
    std::string hl = "";
    hl+=(uint8_t)(u>>8);
    hl+=(uint8_t)(u&0x00ff);
    return hl;
}
restore16int()
inline uint16_t QueryGenerator::restore16int(uint8_t c0,uint8_t c1){return (c0<<8)+c1;}

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