Getting Started
This guide covers how to define your protocol, generate code, and integrate bleRPC into your project.
1. Define Your Protocol
Create a .proto file defining your RPC messages. Each command is a Request/Response pair:
syntax = "proto3";
package blerpc;
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
message FlashReadRequest {
uint32 address = 1;
uint32 length = 2;
}
message FlashReadResponse {
uint32 address = 1;
bytes data = 2;
}
Commands are automatically discovered from matching *Request / *Response pairs. For example, EchoRequest + EchoResponse generates an echo command.
2. Code Generation
The generate-handlers tool generates handler stubs and client code for all supported languages.
Build the generator
cd tools/generate-handlers
go build -o generate-handlers .
Run code generation
./generate-handlers -root /path/to/blerpc
This generates the following files:
| Output | Description |
|---|---|
peripheral_fw/src/generated_handlers.h | C handler declarations + lookup table |
peripheral_fw/src/generated_handlers.c | C weak handler stubs |
peripheral_py/generated_handlers.py | Python handler stubs |
central_py/blerpc/generated/generated_client.py | Python client mixin |
central_android/.../GeneratedClient.kt | Kotlin abstract client |
central_ios/.../GeneratedClient.swift | Swift client protocol extension |
central_flutter/.../generated_client.dart | Dart client mixin |
central_rn/.../GeneratedClient.ts | TypeScript abstract client (React Native) |
central_fw/src/generated_client.h | C central client declarations |
central_fw/src/generated_client.c | C central client implementation |
3. Platform Integration
iOS (Swift)
Dependencies
Add to your Xcode project via Swift Package Manager:
// Package.swift or Xcode > File > Add Package Dependencies
https://github.com/tdaira/blerpc-protocol-swift (from: 0.5.0)
https://github.com/apple/swift-protobuf.git (from: 1.28.0)
Generate Protobuf types
brew install swift-protobuf
protoc --swift_out=YourApp/Proto/ \
--swift_opt=Visibility=Public \
-I proto/ proto/blerpc.proto
Usage
let client = BlerpcClient()
// Scan for peripherals
let devices = try await client.scan()
try await client.connect(device: devices[0])
// Type-safe RPC call
let response = try await client.echo(message: "hello")
print(response.message) // "hello"
// Read flash data
let flash = try await client.flashRead(address: 0, length: 8192)
print(flash.data.count) // 8192
client.disconnect()
Android (Kotlin)
Dependencies
Add to your build.gradle.kts:
repositories {
maven {
url = uri("https://maven.pkg.github.com/tdaira/blerpc-protocol-kt")
credentials { /* GitHub token */ }
}
}
dependencies {
implementation("com.blerpc:blerpc-protocol-kt:0.5.0")
}
Usage
val client = BlerpcClient(context)
// Scan for peripherals
val devices = client.scan()
client.connect(devices[0])
val response = client.echo(message = "hello")
println(response.message) // "hello"
client.disconnect()
Python (macOS / Linux)
Dependencies
pip install bleak protobuf
pip install git+https://github.com/tdaira/blerpc-protocol.git
Generate Protobuf types
protoc --python_out=central_py/blerpc/generated/ \
-I proto/ proto/blerpc.proto
Usage
from blerpc.client import BlerpcClient
client = BlerpcClient()
# Scan for peripherals
devices = await client.scan()
await client.connect(devices[0])
resp = await client.echo(message="hello")
print(resp.message) # "hello"
flash = await client.flash_read(address=0, length=8192)
print(len(flash.data)) # 8192
await client.disconnect()
Flutter (Dart)
Dependencies
Add to your pubspec.yaml:
dependencies:
blerpc_protocol: ^0.6.0
flutter_blue_plus: ^1.35.0
protobuf: ^6.0.0
Generate Protobuf types
dart pub global activate protoc_plugin
protoc --dart_out=lib/proto/ \
-I proto/ proto/blerpc.proto
Usage
final client = BlerpcClient();
// Scan for peripherals
final devices = await client.scan();
await client.connect(devices[0]);
// Type-safe RPC call
final response = await client.echo(message: 'hello');
print(response.message); // "hello"
// Read flash data
final flash = await client.flashRead(address: 0, length: 8192);
print(flash.data.length); // 8192
client.disconnect();
React Native (TypeScript)
Dependencies
Add to your package.json:
{
"dependencies": {
"@blerpc/protocol-rn": "file:../blerpc-protocol-rn",
"protobufjs": "^7.4.0",
"react-native-ble-plx": "^3.2.1",
"react-native-get-random-values": "^1.11.0",
"buffer": "^6.0.3",
"fast-text-encoding": "^1.0.6",
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.7",
"@noble/hashes": "^1.8.0"
}
}
Generate Protobuf types
npx pbjs -t static-module -w commonjs \
-o src/proto/blerpc.js ../proto/blerpc.proto
npx pbts -o src/proto/blerpc.d.ts src/proto/blerpc.js
Usage
import { BlerpcClient } from './client/BlerpcClient';
import { BleTransport } from './ble/BleTransport';
const transport = new BleTransport();
const devices = await transport.scan();
const client = new BlerpcClient(transport);
await client.connect(devices[0]);
// Type-safe RPC call
const response = await client.echo({ message: 'hello' });
console.log(response.message); // "hello"
// Read flash data
const flash = await client.flashRead({ address: 0, length: 8192 });
console.log(flash.data.length); // 8192
client.disconnect();
Zephyr Central (C)
Generated Client
The code generator creates generated_client.h and generated_client.c with typed wrapper functions for all commands. You implement three transport functions (blerpc_rpc_call, blerpc_stream_receive, blerpc_stream_send) and call the generated API:
#include "generated_client.h"
// Type-safe RPC call — protobuf encode/decode is handled
blerpc_EchoResponse resp;
int ret = blerpc_echo("Hello from central!", &resp);
if (ret == 0) {
LOG_INF("Echo: %s", resp.message);
}
// Read flash data (FT_CALLBACK fields handled automatically)
blerpc_FlashReadResponse flash;
uint8_t data_buf[8192];
size_t data_len;
ret = blerpc_flash_read(0, 8192, &flash, data_buf, sizeof(data_buf), &data_len);
// P2C streaming
blerpc_CounterStreamResponse results[20];
size_t count;
ret = blerpc_counter_stream(20, results, 20, &count);
Build
west build -b nrf54l15dk/nrf54l15/cpuapp central_fw
Zephyr Peripheral (C)
Implement handlers
The generated handlers are __attribute__((weak)). Override them in your own source file:
// handlers.c
#include "generated_handlers.h"
#include "blerpc.pb.h"
int handle_echo(const uint8_t *req_data, size_t req_len,
pb_ostream_t *ostream) {
blerpc_EchoRequest req = blerpc_EchoRequest_init_zero;
pb_istream_t stream = pb_istream_from_buffer(req_data, req_len);
if (!pb_decode(&stream, blerpc_EchoRequest_fields, &req))
return -1;
blerpc_EchoResponse resp = blerpc_EchoResponse_init_zero;
strncpy(resp.message, req.message, sizeof(resp.message) - 1);
if (!pb_encode(ostream, blerpc_EchoResponse_fields, &resp))
return -1;
return 0;
}
Build
# nRF54L15
west build -b nrf54l15dk/nrf54l15/cpuapp peripheral_fw
# EFR32xG22E
west build -b xg22_ek2710a peripheral_fw -- -DBOARD_ROOT=/path/to/blerpc
4. Testing with Python Central
The easiest way to verify your peripheral is working:
# Flash the peripheral firmware
west flash
# Run the Python integration tests
cd central_py
python -m pytest tests/test_integration.py -v
Or use the interactive client:
python -c "
import asyncio
from blerpc.client import BlerpcClient
async def main():
client = BlerpcClient()
devices = await client.scan()
await client.connect(devices[0])
resp = await client.echo(message='hello')
print(f'Echo: {resp.message}')
await client.disconnect()
asyncio.run(main())
"