Micro Interconnect & Network Dispatch (Named by inverting 'M' of Microsystems into 'W')
WindRPC is a lightweight RPC framework designed for micro systems.
Built upon Protocol Buffers and NanoPB, WindRPC enables Remote Procedure Calls (RPC) between microcontrollers (MCUs) and high-level applications (C#, JS/TS), specifically designed for small embedded environments.
-
Single YAML-based RPC Descriptor
- Define all message structures and RPC interfaces in a single YAML specification file (
user_spec.yml) without manually writing.protofiles or C headers. - Self-contained design with zero external
importdependencies, enabling compilation without complex include path configurations.
- Define all message structures and RPC interfaces in a single YAML specification file (
-
Full-Code Generation (Server & Multi-Language Clients)
- Protobuf & NanoPB Generation: Automatically generates
.protoschemas and Nanopb static memory option files (.options). - C Server Code Generation: Automatically generates C server dispatchers with O(1) direct lookup (
(service_id << 8) | rpc_id) and callback skeletons for embedded C MCUs (developers only implement business logic callbacks). - Multi-Language Client SDKs: Automatically generates native C# (Async
Taskbased), JavaScript/TypeScript (AsyncPromisebased - Zero Dependencies), and Python (Asyncasynciobased) client SDKs.
- Protobuf & NanoPB Generation: Automatically generates
-
Zero-Heap & Static Memory Architecture
- 100% static memory allocation without dynamic memory allocation (
malloc/free) in generated code and callbacks. - Tailored specifically for resource-constrained microcontrollers (ARM Cortex-M, ESP32, etc.) using NanoPB constraints.
- 100% static memory allocation without dynamic memory allocation (
-
6-Byte Binary Header & Zero-Envelope Dispatch
- Fixed 6-byte raw binary header
[RPC_ID(2B)][SEQ_ID(2B)][PAYLOAD_LEN(2B)]directly preceding Protobuf payload bytes. - Eliminates outer Protobuf envelope decoding overhead and enables instant O(1) lookup table dispatch.
- Fixed 6-byte raw binary header
-
Transport Agnostic & Flexible Framing
- Works seamlessly across serial byte streams (UART, USB-CDC) via optional COBS framing and packet datagram channels (UDP, BLE, TCP).
WindRPC operates across two phases: Build-time Code Generation (from a single YAML specification) and Runtime Packet Dispatch (zero-heap binary frame processing).
flowchart TD
YAML["user_spec.yml<br/>(RPC Descriptor)"] --> GEN["windrpc_gen.py<br/>(WindRPC Generator Engine)"]
GEN --> Proto[".proto & .options Files<br/>(Nanopb Schemas)"]
GEN --> CServer["C Server Engine<br/>(windrpc.h/c, callbacks skeleton)"]
GEN --> Client["Client SDK<br/>JS/TS: WindRpcClient.js (Zero Dep)<br/>C#: WindRpcClient.cs + Data Classes<br/>Python: WindRpcClient.py (asyncio)"]
sequenceDiagram
autonumber
participant Client as Client Application
participant Transport as Physical Channel<br/>(UART / USB-CDC / BLE / UDP)
participant Engine as WindRPC C Server Engine<br/>(C MCU Server)
participant App as Application Callbacks<br/>(windrpc_callbacks.c)
Note over Client, Engine: 1. Request-Response RPC Cycle (e.g. 0x0801)
Client->>Transport: Send Frame [6B Binary Header + Protobuf Payload]
Transport->>Engine: Raw Frame Bytes -> process_incoming_packet()
Engine->>Engine: 1. Header Validation<br/>2. O(1) Combined RPC ID Direct Lookup
Engine->>App: Invoke windrpc_on_get_power_status(req, res)
App-->>Engine: Return 0 (Success) & fill res struct
Engine->>Transport: Response Frame [6B Header + NanoPB Encoded Payload]
Transport-->>Client: Receive Response -> Resolve Promise / Task
Note over Client, Engine: 2. Subscription & Server Push Notification (e.g. 0x0882)
Client->>Transport: 1) Register Listener / Send Subscription RPC (e.g. SubscribeAlerts)
Transport->>Engine: Process Subscription Request & Register Session
Engine-->>Client: Subscription Confirmed
Note over Engine, App: Event Occurs in MCU Firmware
App->>Engine: 2) Trigger windrpc_notify_charging_alert(data, txn)
Engine->>Transport: Push Notification Frame (RPC ID with MSB 0x80)
Transport-->>Client: Receive Event Frame -> Dispatch Subscribed OnNotification Handler
WindRPC avoids recursive Protobuf envelope decoding. Instead, it pairs a 6-byte fixed raw binary header (Little-Endian) directly with a Protobuf payload:
+-------------------+-------------------+-------------------+--------------------------------+
| RPC_ID (2 Bytes) | SEQ_ID (2 Bytes) | PAYLOAD_LEN (2B) | PAYLOAD (Protobuf Binary) |
+-------------------+-------------------+-------------------+--------------------------------+
| 0x03 0x07 | 0x01 0x00 | 0x00 0x02 | [NanoPB-encoded Data Bytes] |
+-------------------+-------------------+-------------------+--------------------------------+
|<-------------- 6-Byte Fixed Raw Binary Header (Little-Endian) ------------->|
- 6-Byte Fixed Binary Header (Non-Protobuf, Little-Endian):
RPC_ID(2B):(service_id << 8) | rpc_id(e.g. Service 7, RPC 3 ->0x0703-> LSB first0x03 0x07)SEQ_ID(2B): Request sequence counter (e.g. 1 ->0x0001-> LSB first0x01 0x00)PAYLOAD_LEN(2B): Payload byte length (e.g. 2 ->0x0002-> LSB first0x02 0x00)
- Protobuf Binary Payload:
- Raw binary payload data serialized via NanoPB/Protobuf for user-defined structs (e.g.
PowerStatus,WifiConfig)
- Raw binary payload data serialized via NanoPB/Protobuf for user-defined structs (e.g.
Pairing a 6-byte Little-Endian raw binary header (matching native ARM Cortex-M endianness) directly with a Protobuf payload eliminates outer Protobuf envelope parsing and reduces NanoPB callback overhead to zero.
WindRPC intentionally leaves data-link-level tasks (such as custom packet framing and integrity checksums) to the user's application layer, avoiding complex protocol layer bloat on microcontrollers:
- Data Link Responsibilities Left to User: Full link-layer protocol stacks and CRC/checksum routines are not built into WindRPC natively. Developers can optionally append CRC16/CRC32 or outer channel wrappers in their custom transport layer when operating over noisy physical mediums (e.g. industrial RS-485).
- Client SDK COBS Utilities: Auto-generated client SDKs (JS/TS, C#, Python) include built-in COBS encoding/decoding utilities (
0x00frame delimiter) for convenience in continuous serial stream workflows (buildCobsFrame/receiveBytes). - C MCU Server COBS Integration: The C MCU server engine (
windrpc.c) is stateless and does not include built-in COBS routines natively. Developers can refer to the Zephyr-targeted C COBS reference implementation logic (https://github.com/micro-artwork/cobs) or use Zephyr's native COBS module (sys/cobs.h). - Raw Binary Direct Transport: For channels where packet boundaries and integrity are guaranteed by the transport (UDP, BLE, TCP), raw 6-byte binary header frames can be transmitted directly without any COBS overhead (
buildRawFrame/receiveRawDatagram).
WindRPC evolved through key design iterations to achieve optimal microcontroller performance:
-
Initial Concept (Nested Envelope Mode):
- In early framework iterations, WindRPC explored nesting messages inside a multi-level Protobuf envelope structure (
ClientMessage->Request->Service->Command), which allowed developers constructing raw Protobuf payloads manually to intuitively match message names to RPC methods. - However, as WindRPC transitioned to a Full-Code Generation policy (auto-generating full C server dispatchers and idiomatic C#, JS/TS client SDKs), manual Protobuf message crafting was eliminated. Making
.protoschemas human-readable at the expense of high NanoPB callback overhead and heavy MCU stack usage proved to be an inefficient trade-off.
- In early framework iterations, WindRPC explored nesting messages inside a multi-level Protobuf envelope structure (
-
Current Official Standard:
- WindRPC pairs a 6-byte Little-Endian fixed raw binary header (
RPC_ID[2] + SEQ_ID[2] + PAYLOAD_LEN[2]) directly with a Protobuf payload. - Outer Protobuf envelope parsing is eliminated. Routing is executed via a 16-bit combined RPC ID (
(service_id << 8) | rpc_id) using an$O(1)$ direct lookup array, achieving maximum execution speed with minimal RAM/Flash footprint and zero callback overhead.
- WindRPC pairs a 6-byte Little-Endian fixed raw binary header (
Microcontrollers parse the 6-byte Little-Endian header in
$O(1)$ integer operations, matching ARM Cortex-M native byte order and BLE GATT conventions.
Estimated memory consumption of the WindRPC Core C Server Engine and NanoPB Runtime (excluding user application messages in user_spec.yml) on Arm Cortex-M microcontrollers (M0+/M3/M4/M33 with GCC -Os optimization):
| Component | ROM (Flash) | RAM (SRAM) | Description |
|---|---|---|---|
NanoPB Core Engine (pb_encode/decode/common) |
~2.5 KB – 3.5 KB | 0 B | Zero runtime heap usage; uses C call stack during encoding/decoding |
WindRPC Core Engine (windrpc.c) |
~1.5 KB – 2.5 KB | ~100 B | 16-bit Combined ID$O(1)$ Direct Lookup Dispatcher & Core Services |
| Frame Transport Buffer | 0 B | ~128 B – 512 B | User-configurable RX/TX buffer (In-place or Double Buffer) |
| Base Core Total | ~4.0 KB – 6.0 KB | ~100 B (+Buffer) | Operates reliably even on ultra-small MCUs (e.g. 16KB Flash / 4KB RAM) |
Important Caveats when using In-place Buffer Mode (
WINDRPC_USE_INPLACE_BUFFER = 1): In-place mode uses a single shared buffer for both RX and TX to minimize RAM consumption on ultra-small MCUs:
- Request Payload Overwrite Hazard: In your C server callback, do NOT assign pointers (shallow copy) from the request message (
req) string/bytes fields to the response message (res). When encoding the response, the RX buffer memory is overwritten, leading to data corruption. Always perform a Deep Copy (memcpy/strncpy) or copy request parameters to local variables before preparing the response.- Half-Duplex Transports Only: Intended strictly for half-duplex, synchronous request-response execution (UART, RS-485).
- Asynchronous Notification Concurrency Warning: Do not trigger
windrpc_notify_*calls from interrupts or other threads while a request-response cycle is encoding. Ensure sequential execution using Mutexes or a single OS WorkQueue.
WindRPC CLI tool can be installed directly from the Git repository:
# Direct installation from Git repository
pip install git+https://github.com/micro-artwork/windrpc.git
# Or clone the repository and install in editable mode
git clone https://github.com/micro-artwork/windrpc.git
cd windrpc
pip install -e .WindRPC uses a single YAML-based RPC Descriptor for your project, eliminating the need to manually write complex .proto files.
Note
user_spec.yml is used as an example filename throughout the documentation. You can name your specification file anything you prefer (e.g. bitnari_spec.yml, my_app.yaml) and pass its path to the CLI via the -s / --user-spec parameter.
package: Root package identifier for namespace and Protobuf schemaconfig: Project configuration settingsservices: List of RPC services, each with a uniqueid(> 6) andnamemessages: Message structures (field tags, scalar/message types, and Nanopb memory constraint options likemax_count,max_length)rpcs: RPC method definitions (id,name,type,command/result)REQUEST_RESPONSE: Standard request-response RPC (Command -> Result)REQUEST_ONLY: One-way fire-and-forget command (Command -> None)NOTIFICATION: Asynchronous server push event notification (Event)
Static Memory Architecture for Embedded Server Stability
- Static Allocation Priority: In microcontroller (MCU) environments, dynamic memory allocation (
malloc/free) leads to memory fragmentation and heap exhaustion over extended runtimes, causing server instability. To maximize server reliability and predictability, WindRPC prioritizes 100% Static Memory Allocation (zero runtime heap usage) by design.- Automatic Fallbacks when Omitted: Even if
nanopb:memory options are omitted in the YAML spec, WindRPC automatically assigns conservative static fallbacks (string: 64B,bytes: 64B,repeated: 16 items) instead of falling back to dynamic NanoPB callbacks. (Configurable globally viaconfig:).- Explicit Max Size Recommendation: To optimize RAM usage and prevent buffer overflows, developers are strongly encouraged to analyze their application domain and explicitly define appropriate maximum bounds (
max_length/max_count) for each variable-length field.
package: my_project
services:
# Service IDs 1 to 6 are reserved for WindRPC core services
# Reserved combined RPC IDs: 0x0000 (System Error), 0x0601 (Core Ping/Version Handshake)
- id: 7
name: led_control
messages:
- name: PixelData
fields:
- number: 1
name: colors
type: uint32
property: repeated
nanopb: { max_count: 64 }
rpcs:
- id: 1
name: display_pixels
type: REQUEST_ONLY
command: PixelData
- id: 8
name: power_manager
messages:
- name: PowerStatus
fields:
- { number: 1, name: voltage_mv, type: uint32 }
- { number: 2, name: is_charging, type: bool }
rpcs:
- id: 1
name: get_power_status
type: REQUEST_RESPONSE
command: types.Empty
result: PowerStatus
- id: 2
name: charging_alert
type: NOTIFICATION
event: PowerStatusWindRPC can generate standalone Protobuf files independently, or generate complete server/client SDK code in a single command.
Note
Running windrpc server or windrpc client automatically generates all required .proto schemas and Nanopb .options files internally as part of the pipeline.
windrpc proto -s user_spec.yml -o protoswindrpc server -s user_spec.yml -o server# C# Client Generation
windrpc client -s user_spec.yml -o client/csharp -l csharp
# JS/TS Client Generation
windrpc client -s user_spec.yml -o client/js -l js
# Python Client Generation
windrpc client -s user_spec.yml -o client/python -l pythonHow to configure prj.conf and CMakeLists.txt when integrating generated WindRPC server code and Protobuf files into a Zephyr RTOS project.
Enable Nanopb and build options:
# Enable Nanopb
CONFIG_NANOPB=y
CONFIG_NANOPB_WITHOUT_64BIT=y
# (Optional) COBS framing for serial/UART transports
CONFIG_COBS=yIn your Zephyr project's CMakeLists.txt, include the Nanopb CMake module, compile the generated .proto files into Nanopb C code, and link the WindRPC C server sources to your app target:
if (CONFIG_NANOPB)
# 1) Include Zephyr Nanopb CMake module list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)
include(nanopb)
# 2) Compile generated .proto files with Nanopb generator
zephyr_nanopb_sources(app RELPATH protos protos/<package_name>/windrpc/types/types.proto
protos/<package_name>/windrpc/service/common.proto
protos/<package_name>/windrpc/service/<your_service>.proto
protos/<package_name>/windrpc/core/windrpc.proto)
# 3) Include WindRPC C server sources and include directories
include_directories(src/windrpc)
target_sources(app PRIVATE src/windrpc/windrpc.c
src/windrpc/windrpc_callbacks.c
src/windrpc/windrpc_notify.c)
endif()For comprehensive framework specifications and developer guides, refer to the unified reference manual:
- windrpc_manual.md: Comprehensive reference manual covering specification authoring, 6-byte binary header mechanics, C server engine callbacks, and client SDK integration (JS/TS & C#).
WindRPC currently has limited maintainer availability for external code reviews or PR processing.
- Pull Requests: Unsolicited PRs may be automatically closed without review.
- Future Open Roadmap: The timeline for accepting external contributions is TBD. We plan to open public contributions when the project matures into a broader community-focused framework. Thank you for your understanding.
This project is released under the MIT License.