Imagine writing a software package that sends a single rpc_request node once every five minutes. You could easily track the status of your RPC request by simply waiting for the next rpc_ok or rpc_error node from the device.

However, what if we are required to send 100 requests in a one-minute timeframe? Identifying success or failure is no longer a trivial task. This is especially true for operations that result in partial failure, where only a portion of the requests succeed.

When using the HTTP protocol, the solution is simple - open a connection, start a request, and then close the connection when finished. The situation is more complicated over MQTT, which is a persistent, session-based protocol where many requests are sent over the same TCP socket.

CeleryScript authors can solve this problem by tagging each outbound request with a unique label arg. You can think of this as a sort of request ID.

Multiplexing requests with the label arg

FarmBot has the ability to send many commands over a single MQTT channel. Messages do not follow a call/response cycle seen with HTTP servers. Unlike HTTP, MQTT connections are persistent, full duplex connections. If you send three commands to a FarmBot, they might not come back in the order that they were received.

Request order != response order:

     | BROWSER  Request "ABC"             DEVICE
     |        ------------------------->
     |          Request "DEF"
Time |        ------------------------->
     |          Request "GHI"
     |        ------------------------->
     |          Response to "DEF"
     |        <-------------------------
     v          Response to "ABC"
              <-------------------------
                Response to "GHI"
              <-------------------------

To make sense of which messages have been acknowledged, the rpc_request, rpc_ok, and rpc_error nodes all contain a label argument. The label serves as a unique identifier for a message. Assigning unique IDs to each RPC message allows re-assembly of message order on arrival.

In the case of an rpc_error, it allows us to specify not only that an error has occurred, but also which command caused the error to occur.

Here is an example of a move_relative command sent to a device over MQTT:

move_relative RPC:

// Message published to MQTT channel:
// '/bot/device_123/from_clients'
{
    kind: "rpc_request",
    // Any unique identifier can be used as a label.
    // UUIDs are highly recommended.
    args: { label: "adslkjhfalskdha" },
    body: [
      { kind: "move_relative", args: { x: 0, y: 0, z: 0, speed: 100 } }
    ]
}
// ... after some time the bot will respond on MQTT channel `/bot/device_123/from_device`

{
    kind: "rpc_ok",
    // The "label" here matches the "label" of the move_relative request (shown above).
    args: { label: "adslkjhfalskdha" }
}

What’s next?