C++ API reference
Note: All C++ code shown here is generated by lrpcg. You do not write it — you implement the abstract methods it declares.
Overview
lrpcg cpp generates a set of header files for each interface definition. For a definition named example with services math and sensor, the output looks like (some files omitted for clarity):
treeView-beta
"generated"
"example"
"lrpccore"
"example.hpp"
"math_shim.hpp"
"sensor_shim.hpp"
"..."
The top-level include example.hpp pulls in all of the other files and is the recommended way to include code generated by LotusRPC in your project. The *_shim.hpp files contain the abstract base classes for the services.
Server class
The top-level include exposes a type alias for the fully configured server:
// example.hpp (generated)
namespace ex
{
using example = lrpc::Server<...>;
}
lrpc::Server is a template class parameterized with buffer sizes and the meta service. You interact with it through the following interface:
lrpcTransmit (pure virtual)
virtual void lrpcTransmit(lrpc::span<const uint8_t> bytes) = 0;
You must subclass the generated server and implement this method. LotusRPC calls it whenever it has a frame ready to send to the client. Wire it to your hardware transmit routine (UART, SPI, TCP socket, etc.).
class MyServer : public ex::example
{
void lrpcTransmit(lrpc::span<const uint8_t> bytes) override
{
uart_write(bytes.data(), bytes.size());
}
};
registerService
void registerService(Service& service);
Registers a service instance with the server. Call once per service before processing any data. The service object must outlive the server.
lrpcReceive
void lrpcReceive(uint8_t byte); // single byte
void lrpcReceive(lrpc::span<const uint8_t> bytes); // span of bytes (lvalue container implicitly converted to span)
template <typename TContainer>
void lrpcReceive(const TContainer &bytes); // container of bytes
Feeds incoming bytes to the server. Call from your receive interrupt or polling loop. LotusRPC handles framing internally — pass bytes as they arrive.
Service class
For each service in the definition, lrpcg generates a shim class. You derive from it and implement its pure virtual methods. For a service named math with one function add(int32_t a, int32_t b) -> int32_t:
// math_shim.hpp (generated)
namespace ex
{
class math_shim : public lrpc::Service
{
protected:
virtual int32_t add(int32_t a, int32_t b) = 0;
};
}
Your implementation:
class MathService : public ex::math_shim
{
protected:
int32_t add(int32_t a, int32_t b) override
{
return a + b;
}
};
Single return value
functions:
- name: get_temperature
returns:
- { name: temp, type: float }
Generated shim method:
virtual float get_temperature() = 0;
Implementation:
float get_temperature() override
{
return read_sensor();
}
Multiple return values
functions:
- name: get_status
returns_alias: Status
returns:
- { name: code, type: uint8_t }
- { name: message, type: string }
Generated shim method:
using Status = std::tuple<uint8_t, lrpc::string_view>;
virtual Status get_status() = 0;
Implementation:
Status get_status() override
{
return {0, "OK"};
}
Client stream
The client sends data to the server. The server receives it through a pure virtual method and can optionally request the client to stop.
streams:
- name: log_data
origin: client
finite: true
params:
- { name: entry, type: string }
Generated shim methods:
// Called for each incoming message. `final` is true for the last message (finite streams only).
virtual void log_data(lrpc::string_view entry, bool final) = 0;
// Call this to ask the client to stop sending (optional).
void log_data_requestStop();
Implementation:
void log_data(lrpc::string_view entry, bool final) override
{
store_log(entry);
if (final) { flush_log(); }
}
Server stream
The client initiates and terminates the stream. The server sends data back by calling a non-virtual response method.
streams:
- name: sensor_data
origin: server
params:
- { name: value, type: uint16_t }
Generated shim methods:
// Called when the client sends 'start'.
virtual void sensor_data() = 0;
// Called when the client sends 'stop'.
virtual void sensor_data_stop() = 0;
// Call this to send a data message to the client.
void sensor_data_response(uint16_t value);
Implementation:
void sensor_data() override
{
streaming_ = true;
}
void sensor_data_stop() override
{
streaming_ = false;
}
// Somewhere in your application loop:
void on_new_measurement(uint16_t v)
{
if (streaming_) { sensor_data_response(v); }
}
Finite server stream
For a finite server stream, the response method has an additional bool final parameter:
void sensor_data_response(uint16_t value, bool final);
Pass final = true with the last message to signal end of stream.
Type mapping
The table below shows how LotusRPC definition types map to C++ types in function parameters and return values.
| LotusRPC type | C++ type |
|---|---|
(u)intx_t |
(u)intx_t |
bool, float, double |
bool, float, double |
| enum | enum class |
| struct | C++ struct |
| string (fixed or auto size) | lrpc::string_view |
| byte array | lrpc::bytearray |
| array (count N) | lrpc::span<const T> |
| optional | lrpc::optional<T> |
| multiple returns | std::tuple<T1, T2, ...> |
Type aliases
| Alias | Underlying type | Notes |
|---|---|---|
lrpc::byte |
uint8_t (default) |
Configurable via byte_type setting |
lrpc::bytearray |
etl::span<const lrpc::byte> |
View over a byte buffer |
lrpc::string_view |
etl::string_view |
View over a string buffer |
lrpc::span<T> |
etl::span<T> |
Generic span |
lrpc::array<T, N> |
std::array<T, N> |
Fixed-size array |
lrpc::optional<T> |
etl::optional<T> |
Optional value |
Ownership and lifetimes
In LotusRPC, incoming bytes are decoded from the server receive buffer and forwarded to your function implementation as arguments. Return values are encoded back into the server transmit buffer. LotusRPC uses value semantics as much as possible, with a few exceptions for efficiency.
Note: Returning a reference of any kind to a local variable leads to undefined behavior in C++. The ownership rules below follow the same principle.
| Type | Parameter semantics | Return semantics |
|---|---|---|
(u)intx_t |
Passed by value | Returned by value |
bool, float, double |
Passed by value | Returned by value |
| enum | Passed by value | Returned by value |
| Struct | Decoded into a local copy, passed by const& |
Returned by value |
| String (fixed or auto size) | lrpc::string_view into the receive buffer |
lrpc::string_view — caller must ensure the viewed string outlives the function |
| Array | Decoded into a local copy (for alignment), passed as lrpc::span |
lrpc::span — same lifetime rules as string |
| Bytearray | lrpc::bytearray (span) into the receive buffer |
lrpc::bytearray — same lifetime rules as string |
For strings, arrays and bytearrays: the parameter can be used safely inside the function, but must be copied if needed beyond the call. Struct fields that are strings or bytearrays follow the same rules as standalone strings/bytearrays.
Examples
For a full end-to-end walkthrough — definition, code generation, service implementation, and Python client — see the Math service example. For an example on real embedded hardware with HAL-based transport, see the STM32 example on real hardware.