Skip to content

Servo Movement


Client → Server Servo Movement Query Format

The servo movement query (which uses the 4-byte code SRVP) is used by the client to issue movement commands for the selected mcu. The query has the structure:

!s-SRVP-[NUMBER_OF_MOVEMENTS]-[SERVO_ID:SERVO_POS]-<...>-[SERVO_ID:SERVO_POS]-e!
  • [NUMBER_OF_MOVEMENTS] → A single uint8_t indicating how many servo movements the query contains. Since it cannot be zero, it's not affected by the offset.
  • [SERVO_ID:SERVO_POS] → The information pair SERVO_ID, the id of the servo the client wants to move; and SERVO_POS, the position the servo has to take. These two values are separated by the ASCII character : (uint8_t c=58), and for multiple movements, each information pair SERVO_ID:SERVO_POS is separated by a dash - (uint8_t c=45) from the information pair of the following servo movement. Both SERVO_ID and SERVO_POS are single uint8_t values, and must have the positive +1 offset applied, as they can take value zero.
!s-SRVP-[NUMBER_OF_MOVEMENTS]-[SERVO_ID:PWM_SIGNAL]-<...>-[SERVO_ID:PWM_SIGNAL]-e!
  • [NUMBER_OF_MOVEMENTS] → A single uint8_t indicating how many servo movements the query contains. Since it cannot be zero, it's not affected by the offset.
  • [SERVO_ID:PWM_SIGNAL] → The information pair SERVO_ID, the id of the servo the client wants to move; and PWM_SIGNAL, the PWM signal for the given servo ID. These two values are separated by the ASCII character : (uint8_t c=58), and for multiple movements, each information pair SERVO_ID:PWM_SIGNAL is separated by a dash - (uint8_t c=45) from the information pair of the following servo movement. Both SERVO_ID (a single uint8_t value) and PWM_SIGNAL (a uint16_t value) must have the positive +1 offset applied, as they can take value zero.

Due to the server function of query parameter validation, the response for this query can take two forms:

If during validation, the server rejects a query for any reason, it will return a single NACK control query to the client, ending the SRVP process. The defined possible NACK codes for this first reponse are:

  • _NACK_InvalidQuery (uint8_t c=255) → If the query format is invalid: contains null-bytes, invalid header or tail sequences, or the query function code is not recognized.
  • _NACK_NoActiveMCU (uint8_t c=254) → If client has not selected a mcu before issuing the movement command.
  • _NACK_InvalidParameter (uint8_t c=252) → If the query parameters are out of range: a servo id exceeds the selected mcu servo count, or a servo position out of the range [1→180].
  • _NACK_ServoCountMissmatch (uint8_t c=251) → If the movement order tries to move more servos than the selected mcu has.
  • _NACK_NoMCUInfo (uint8_t c=250) → If the selected mcu is a "DumbMCU", and there's no PWM information available in the server database.
  • _NACK_MCUOffline (uint8_t c=249) → If the selected mcu is not currently connected to the server.

On the other hand, if the server validates the parameters, it will return a first ACK control query to the client and forward the SRVP query to the target mcu. Once the mcu performs the movement, it will reply with a control query to the server, which is then be relayed back to the client as a second control response. For this second control query, the defined possible NACK codes are:

  • _NACK_InvalidQuery (uint8_t c=255) → Should never happen: If the query format is invalid: contains null-bytes, invalid header or tail sequences, or the query function code is not recognized.
  • _NACK_NoActiveMCU (uint8_t c=254) → If client has not selected a mcu before issuing the movement command. Would happen if another client selects the same mcu as the current client.
  • _NACK_MCUOffline (uint8_t c=249) → If the selected mcu is not currently connected to the server. Would happen if the mcu disconnects between the client query validation and the server message to the mcu.
  • _NACK_ErrorContactingMCU (uint8_t c=248) → If the selected mcu is not currently connected to the server.

During the second control response, both _NACK_NoActiveMCU and _NACK_MCUOffline represent time-of-check time-of-use conditions catched by the server during query dispatch.

Server → MCU Servo Movement Query Format

The servo movement query sent by the client must be translated from the client → server into the server → mcu format before forwarding it to the mcu, which makes the query take the form:

-m-[NUMBER_OF_MOVEMENTS]-[SERVO_ID:SERVO_POS]-<...>-[SERVO_ID:SERVO_POS]-!
-m-[NUMBER_OF_MOVEMENTS]-[SERVO_ID:PWM_SIGNAL]-<...>-[SERVO_ID:PWM_SIGNAL]-!

Where the query parameters [NUMBER_OF_MOVEMENTS], [SERVO_ID:SERVO_POS] and [SERVO_ID:PWM_SIGNAL] remain unchanged and adhere to the same rules as the client → server format.

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;}

Examples: SRVP Communication Overview

The servo movement query takes on a different communication path depending on whether the server validation passes or fails. The possible schemas are:

For instance, the movement query !s-SRVP-2-9:13-7:18-e! would translate to "Perform 2 servo movements: move servo ID 8 to position 12, and servo ID 6 to position 17". Assuming the parameters are valid, and the selected mcu is online, the query validation would succeed, returning the first ACK to the client while forwarding the SRVP query to the mcu. Once the mcu completes the movements, it will reply with a positive control query, which will be relayed back to the client as a second ACK.

On the other hand, if the user sends the query !s-SRVP-1-3:187-e! the movement order (move servo id=2, to position 186) would be invalid, as the servo position is out-of-range. In this case, the server validation would catch it, returning a negative control code (_NACK_InvalidParameter) to the client, and ending the SRVP query "process".

The final option is that, once validated by the server, the mcu deems the movement order invalid. This would lead to a positive first control response, followed by a negative control query. In this case, similar to the SRVP rejection by server, no movement is performed.