Implementing remote procedure calls

A specific API can be used remotely if RPC encoders and RPC decoders are provided for it. On one side, there are encoders that encode parameters and send commands or events. On the other side, there are decoders that decode and execute a specific procedure.

The main goal of nRF RPC API is to allow the creation of RPC encoders and RPC decoders.

Encoders and decoders are grouped. Each group contains functions related to a single API, such as Bluetooth or entropy. A group is created with the NRF_RPC_GROUP_DEFINE macro. Grouping allows you to logically divide the remote API, but also increases performance of nRF RPC.

RPC encoders

RPC encoders encode commands and events into serialized packets. Creating an encoder is similar for all packet types. To create an encoder, complete the following steps:

  1. Allocate a buffer using nrf_rpc_alloc_tx_buf().

  2. Encode parameters directly into the buffer or use the zcbor library.

The packet is sent using one of the sending functions: nrf_rpc_cmd(), nrf_rpc_cbor_evt(), or similar.

After sending the command, a response is received, so it must be parsed. There are two ways to parse a response.

The first way is to provide a response handler in the parameters of nrf_rpc_cmd() or nrf_rpc_cbor_cmd(). It is called before nrf_rpc_cmd() returns. It can be called from a different thread.

Another way of parsing a response is to call nrf_rpc_cmd_rsp() or nrf_rpc_cbor_cmd_rsp(). The output of these functions contains the response. After parsing it, the nrf_rpc_decoding_done() or nrf_rpc_cbor_decoding_done() functions must be called to indicate that parsing is completed and the buffers holding the response can be released.

Events have no response, so they need no additional action after sending them.

The following is a sample command encoder created using the nRF RPC CBOR API. The function remotely adds 1 to the input parameter and puts the result in the output parameter. It returns 0 on success or a negative error code if communication with the remote side failed.

/* Helper define holding maximum CBOR encoded packet length
 * for this sample.
 */
#define MAX_ENCODED_LEN 16

/* Defines a group that contains functions implemented in this
 * sample.
 */
NRF_RPC_GROUP_DEFINE(math_group, "sample_math", &transport, NULL, NULL, NULL);

/* Defines a helper structure to pass the results.
 */
struct remote_inc_result {
        int err;
        int output;
};

int remote_inc(int input, int *output)
{
        struct remote_inc_result result;
        struct nrf_rpc_cbor_ctx ctx;

        NRF_RPC_CBOR_ALLOC(&math_group, ctx, MAX_ENCODED_LEN);

        if (zcbor_int32_put(&ctx.zs, input)) {
                if (!nrf_rpc_cbor_cmd(&math_group, MATH_COMMAND_INC, &ctx,
                                remote_inc_rsp, &result)) {
                        *output = result.output;
                        return result.err;
                } else {
                        return -NRF_EINVAL;
                }
        }
        return -NRF_EINVAL;
}

The above code uses the remote_inc_rsp function to parse the response. The following code shows how this function might look.

static void remote_inc_rsp(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx,
                           void *handler_data)
{
        struct remote_inc_result *result =
                (struct remote_inc_result *)handler_data;

        if (zcbor_int32_decode(ctx->zs, &result->output)) {
                result->err = 0;
        } else {
                result->err = -NRF_EINVAL;
        }
}

RPC decoders

RPC decoders are registered with macros NRF_RPC_CMD_DECODER, NRF_RPC_CBOR_EVT_DECODER, or similar, depending on what kind of decoder it is. Decoders are called automatically when a command or event with a matching ID is received. Command decoders must send a response.

An RPC decoder associated with the example above can be implemented in the following way:

/* Defines a group that contains functions implemented in this
 * sample. Second parameter have to be the same in both remote
 * and local side.
 */
NRF_RPC_GROUP_DEFINE(math_group, "sample_math", &transport, NULL, NULL, NULL);


static void remote_inc_handler(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, void* handler_data)
{
        int err;
        int input = 0;
        int output;
        struct nrf_rpc_cbor_ctx nctx;

        /* Parsing the input */

        zcbor_int32_decode(ctx->zs, &input)

        nrf_rpc_cbor_decoding_done(group, ctx);

        /* Actual hard work is done in below line */

        output = input + 1;

        /* Encoding and sending the response */

        NRF_RPC_CBOR_ALLOC(group, nctx, MAX_ENCODED_LEN);

        zcbor_int32_put(&nctx.zs, output))

        err = nrf_rpc_cbor_rsp(group, &nctx);

        if (err < 0) {
                fatal_error(err);
        }
}

NRF_RPC_CBOR_CMD_DECODER(math_group, remote_inc_handler,
                         MATH_COMMAND_INC, remote_inc_handler, NULL);