A multi-language code generation framework that converts Protocol Buffer (.proto) files into serialization/deserialization code for C, C++, TypeScript, Python, and GraphQL. It provides framing and parsing utilities for structured message communication.
Install from PyPI:
pip install struct-frameNote: The package is named struct-frame (with hyphen) on PyPI, but the Python module uses an underscore: struct_frame. Use python -m struct_frame to run the code generator.
# Generate code for Python
python -m struct_frame examples/robot.proto --build_py --py_path generated/py
# Generate code for multiple languages
python -m struct_frame examples/robot.proto --build_c --build_cpp --build_ts --build_py
# Get help
python -m struct_frame --helpIf you want to contribute or modify the code generator itself, see the Development Guide for instructions on cloning the repository and setting up a development environment.
The project includes a test suite that validates code generation, compilation, and serialization across all supported languages:
# Run all tests
python test_all.py
# Run with verbose output
python tests/run_tests.py --verbose
# Skip specific languages
python tests/run_tests.py --skip-ts --skip-c --skip-cpp
# Generate code only (no compilation/execution)
python tests/run_tests.py --generate-onlySee tests/README.md for detailed test documentation.
The project uses GitHub Actions to automatically run the full test suite on:
- Every push to the
mainbranch - Every pull request targeting the
mainbranch
The CI pipeline:
- Sets up Python 3.11 and Node.js 20
- Installs system dependencies (GCC, G++)
- Installs Python dependencies (proto-schema-parser)
- Installs Node.js dependencies
- Runs the complete test suite (
python test_all.py) - Uploads test artifacts for debugging
You can view test results in the "Actions" tab of the GitHub repository. Test artifacts (generated code and binary files) are available for download for 5 days after each run.
Struct Frame provides high-level SDKs for simplified message communication:
- TypeScript/JavaScript: Promise-based with UDP, TCP, WebSocket, and Serial transports
- Python: Both sync and async implementations with socket, pyserial, websockets support
- C++: Header-only with observer/subscriber pattern, ideal for embedded systems
- C#: Async/await-based for .NET Core, Xamarin, and MAUI applications
See the SDK Overview for details.
Struct Frame provides a message framing system for reliable communication over serial links, network sockets, or any byte stream. Framing solves the fundamental problem of determining where messages begin and end in a continuous data stream.
When sending structured data over a communication channel, you need to:
- Identify message boundaries - Where does one message end and the next begin?
- Validate message integrity - Is the received data complete and uncorrupted?
- Route messages by type - What kind of message is this and how should it be processed?
Struct Frame's framing system addresses these challenges with a cross-platform implementation.
The framing system uses a two-level architecture:
-
Frame Type (Framer): Determines the number of start bytes for synchronization
- Basic: 2 start bytes
[0x90] [0x70+PayloadType] - Tiny: 1 start byte
[0x70+PayloadType] - None: 0 start bytes (relies on external synchronization)
- Basic: 2 start bytes
-
Payload Type: Defines the header/footer structure after start bytes
- Minimal, Default, ExtendedMsgIds, ExtendedLength, Extended
- SysComp, Seq, MultiSystemStream, ExtendedMultiSystemStream
The recommended frame format uses a simple but effective structure:
[Start1=0x90] [Start2=0x71] [Length] [Message ID] [Payload Data...] [CRC1] [CRC2]
Frame Components:
- Start Bytes (0x90, 0x71): Synchronization markers to identify frame boundaries
- Length: Payload length (1 byte, up to 255 bytes)
- Message ID: Unique identifier (0-255) that maps to specific message types
- Payload: The actual serialized message data (variable length)
- Fletcher Checksum: 2-byte error detection using Fletcher-16 algorithm
Example Frame Breakdown:
Message: vehicle_heartbeat (ID=42) with 4 bytes of data [0x01, 0x02, 0x03, 0x04]
Frame: [0x90] [0x71] [0x04] [0x2A] [0x01, 0x02, 0x03, 0x04] [0x7F] [0x8A]
Start1 Start2 Len ID=42 Payload Data Checksum
| Payload Type | Structure | Overhead | Use Case |
|---|---|---|---|
| Minimal | [MSG_ID] [PACKET] |
1 | Fixed-size messages, trusted environments |
| Default | [LEN] [MSG_ID] [PACKET] [CRC] |
4 | Standard format (recommended) |
| ExtendedMsgIds | [PKG_ID] [LEN] [MSG_ID] [PACKET] [CRC] |
5 | Large systems with many message types |
| ExtendedLength | [LEN16] [MSG_ID] [PACKET] [CRC] |
5 | Large payloads (up to 64KB) |
| SysComp | [SYS_ID] [COMP_ID] [LEN] [MSG_ID] [PACKET] [CRC] |
6 | Multi-vehicle networks |
| Seq | [SEQ] [LEN] [MSG_ID] [PACKET] [CRC] |
5 | Packet loss detection |
| MultiSystemStream | [SEQ] [SYS] [COMP] [LEN] [MSG_ID] [PACKET] [CRC] |
7 | Multi-vehicle streaming |
See Framing Documentation for the complete frame format reference.
The frame parser implements a state machine to handle partial data and synchronization recovery:
stateDiagram-v2
[*] --> LOOKING_FOR_START_BYTE
LOOKING_FOR_START_BYTE --> GETTING_HEADER: Found 0x90
GETTING_HEADER --> GETTING_PAYLOAD: Got Message ID
GETTING_PAYLOAD --> LOOKING_FOR_START_BYTE: Complete Frame
GETTING_HEADER --> LOOKING_FOR_START_BYTE: Invalid Message ID
GETTING_PAYLOAD --> LOOKING_FOR_START_BYTE: Checksum Failure
State Descriptions:
- LOOKING_FOR_START_BYTE: Scanning for frame start marker (0x90)
- GETTING_HEADER: Processing message ID and calculating expected frame length
- GETTING_PAYLOAD: Collecting payload data and checksum bytes
This design handles common real-world issues like:
- Partial frame reception (data arrives in chunks)
- Frame corruption (invalid start bytes, checksum mismatches)
- Synchronization loss (automatic recovery when frames are corrupted)
python -m struct_frame examples/myl_vehicle.proto --build_py
# Use generated Python classes directlypython -m struct_frame examples/myl_vehicle.proto --build_ts
npx tsc examples/index.ts --outDir generated/
node generated/examples/index.jspython -m struct_frame examples/myl_vehicle.proto --build_c
gcc examples/main.c -I generated/c -o main
./mainpython -m struct_frame examples/myl_vehicle.proto --build_cpp
g++ -std=c++17 examples/main.cpp -I generated/cpp -o main
./mainpython -m struct_frame examples/myl_vehicle.proto --build_gql
# Use generated .graphql schema files| Feature | C | C++ | TypeScript | Python | C# | GraphQL | Status |
|---|---|---|---|---|---|---|---|
| Core Types | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | Stable |
| String | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | Stable |
| Enums | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | Stable |
| Enum Classes | N/A | ✓ | N/A | N/A | ✗ | N/A | Stable |
| Nested Messages | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | Stable |
| Message IDs | ✓ | ✓ | ✓ | ✓ | ✗ | N/A | Stable |
| Message Serialization | ✓ | ✓ | ✓ | ✓ | ✗ | N/A | Stable |
| Flatten | N/A | N/A | N/A | ✓ | ✗ | ✓ | Partial |
| Arrays | ✓ | ✓ | Partial | ✓ | ✗ | ✓ | Stable |
Legend:
- ✓ - Feature works as documented
- Partial - Basic functionality works, some limitations
- ✗ - Feature not yet available
- N/A - Not applicable for this language
The BasicDefault frame format (recommended) provides a robust framing protocol:
Byte 0: Start Byte 1 (0x90)
- Fixed synchronization marker
- Allows parser to identify frame boundaries
- Recovery point after frame corruption
Byte 1: Start Byte 2 (0x71 for Default payload type)
- Second sync byte encodes payload type
- 0x70 = Minimal, 0x71 = Default, 0x72 = ExtendedMsgIds, etc.
- Allows different frame formats on same channel
Byte 2: Length (0x00-0xFF)
- Payload length in bytes
- Allows receiver to know frame size
Byte 3: Message ID (0x00-0xFF)
- Maps to specific message types in proto definitions
- Used for routing and deserialization
- Must match `option msgid = X` in proto message
Byte N+4: Fletcher Checksum Byte 1
Byte N+5: Fletcher Checksum Byte 2
- Fletcher-16 checksum algorithm
- Calculated over Length + Message ID + Payload
- Provides error detection for corruption
Total Frame Size = Header + Payload + Footer
For BasicDefault (recommended):
- Header: 4 bytes (2 start bytes + length + message ID)
- Payload: Variable (depends on message content)
- Footer: 2 bytes (Fletcher checksum)
Example Calculations:
message SimpleHeartbeat {
option msgid = 1;
uint32 device_id = 1; // 4 bytes
bool alive = 2; // 1 byte
}
// Total: 4 (header) + 5 (payload) + 2 (footer) = 11 bytes| Feature | C | C++ | TypeScript | Python | C# | Status | Notes |
|---|---|---|---|---|---|---|---|
| Frame Encoding | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | All languages can create frames |
| Frame Parsing | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | State machine implementation |
| Checksum Validation | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Fletcher-16 algorithm |
| Sync Recovery | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Auto-recovery from corruption |
| Partial Frame Handling | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Handles chunked data streams |
| Message ID Routing | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Automatic message type detection |
| Buffer Management | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Fixed-size buffers prevent overflow |
| Cross-Language Compatibility | ✓ | ✓ | ✓ | ✓ | ✗ | Stable | Frames interoperate between languages |
Struct Frame supports multiple frame types and payload types for different use cases:
Frame Types:
- Basic Frame: 2 start bytes (0x90, 0x70+PayloadType) - Recommended for most applications
- Tiny Frame: 1 start byte (0x70+PayloadType) - Constrained environments
- None Frame: 0 start bytes - Trusted point-to-point links
Payload Types:
- Default: Length + Message ID + Payload + CRC - Recommended
- Minimal: Message ID + Payload only - Fixed-size messages
- Extended: Package ID + 2-byte length + Message ID + Payload + CRC - Large systems
- SysComp: System/Component IDs for multi-vehicle networks (MAVLink-style)
- MultiSystemStream: Sequence + SysComp for streaming with loss detection
See Framing Documentation for the complete format reference.
Let's trace through a message encoding and parsing example:
Proto Definition:
message VehicleStatus {
option msgid = 42;
uint32 vehicle_id = 1;
float speed = 2;
bool engine_on = 3;
}Message Data:
vehicle_id = 1234 (0x04D2) -> [0xD2, 0x04, 0x00, 0x00] (little-endian)
speed = 65.5 -> [0x00, 0x00, 0x83, 0x42] (IEEE 754 float)
engine_on = true -> [0x01]
Total payload: 9 bytes
BasicDefault Frame Structure:
Position: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14]
Data: 90 71 09 2A D2 04 00 00 00 00 83 42 01 7E C9
│ │ │ │ └─────── Payload (9 bytes) ──────────────┘ │ │
│ │ │ └─ Message ID (42 = 0x2A) │ │
│ │ └─ Length (9 = 0x09) │ │
│ └─ Start Byte 2 (0x71 = Default payload type) │ │
└─ Start Byte 1 (0x90) └────┘
Checksum
Checksum Calculation (Fletcher-16):
Input: [0x09, 0x2A, 0xD2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x83, 0x42, 0x01]
(calculated over length, message ID, and payload)
Result: [0x7E, 0xC9]
# Import generated classes
from myl_vehicle_sf import VehicleStatus
from struct_frame_parser import FrameParser, BasicPacket
# Create message
msg = VehicleStatus()
msg.vehicle_id = 1234
msg.speed = 65.5
msg.engine_on = True
# Encode to frame
packet = BasicPacket()
frame_bytes = packet.encode_msg(msg)
print(f"Frame: {[hex(b) for b in frame_bytes]}")
# Parse frame (simulate byte-by-byte reception)
parser = FrameParser({0x90: BasicPacket()}, {42: VehicleStatus})
for byte in frame_bytes:
result = parser.parse_char(byte)
if result:
print(f"Parsed: vehicle_id={result.vehicle_id}, speed={result.speed}")import * as mv from './generated/ts/myl_vehicle.sf';
import { struct_frame_buffer, parse_char } from './generated/ts/struct_frame_parser';
// Create and encode message
let tx_buffer = new struct_frame_buffer(256);
let msg = new mv.VehicleStatus();
msg.vehicle_id = 1234;
msg.speed = 65.5;
msg.engine_on = true;
mv.VehicleStatus_encode(tx_buffer, msg);
// Parse frame
let rx_buffer = new struct_frame_buffer(256);
for (let i = 0; i < tx_buffer.size; i++) {
if (parse_char(rx_buffer, tx_buffer.data[i])) {
let parsed = mv.VehicleStatus_decode(rx_buffer.msg_data);
console.log(`Parsed: vehicle_id=${parsed.vehicle_id}, speed=${parsed.speed}`);
}
}#include "myl_vehicle.sf.h"
#include "struct_frame_parser.h"
// Create message
VehicleStatus msg = {0};
msg.vehicle_id = 1234;
msg.speed = 65.5f;
msg.engine_on = true;
// Encode to frame
uint8_t frame_buffer[256];
size_t frame_size = basic_frame_encode(frame_buffer, 42, (uint8_t*)&msg, sizeof(msg));
// Parse frame
packet_state_t parser = {0};
// ... initialize parser ...
for (size_t i = 0; i < frame_size; i++) {
msg_info_t info = parse_char(&parser, frame_buffer[i]);
if (info.valid) {
VehicleStatus* parsed = (VehicleStatus*)info.msg_loc;
printf("Parsed: vehicle_id=%d, speed=%.1f\n", parsed->vehicle_id, parsed->speed);
}
}#include "myl_vehicle.sf.hpp"
#include "struct_frame.hpp"
// Create message
VehicleStatus msg{};
msg.vehicle_id = 1234;
msg.speed = 65.5f;
msg.engine_on = true;
// Encode to frame
uint8_t frame_buffer[256];
StructFrame::BasicPacket format;
StructFrame::EncodeBuffer encoder(frame_buffer, sizeof(frame_buffer));
bool success = encoder.encode(&format, VEHICLE_STATUS_MSG_ID, &msg, sizeof(msg));
// Parse frame using FrameParser
StructFrame::FrameParser parser(&format, [](size_t msg_id, size_t* size) {
return StructFrame::get_message_length(msg_id, size);
});
for (size_t i = 0; i < encoder.size(); i++) {
StructFrame::MessageInfo info = parser.parse_byte(frame_buffer[i]);
if (info.valid) {
VehicleStatus* parsed = reinterpret_cast<VehicleStatus*>(info.msg_location);
std::cout << "Parsed: vehicle_id=" << parsed->vehicle_id
<< ", speed=" << parsed->speed << std::endl;
}
}import serial
from struct_frame_parser import FrameParser
# Setup serial connection
ser = serial.Serial('/dev/ttyUSB0', 115200)
parser = FrameParser(packet_formats, message_definitions)
# Continuous parsing loop
while True:
if ser.in_waiting:
byte = ser.read(1)[0]
result = parser.parse_char(byte)
if result:
handle_message(result)import * as net from 'net';
import { struct_frame_buffer, parse_char } from './struct_frame_parser';
const client = net.createConnection({port: 8080}, () => {
console.log('Connected to server');
});
let rx_buffer = new struct_frame_buffer(1024);
client.on('data', (data: Buffer) => {
for (let byte of data) {
if (parse_char(rx_buffer, byte)) {
// Process complete message
handleMessage(rx_buffer.msg_data);
}
}
});src/struct_frame/- Core code generation frameworkgenerate.py- Main parser and validation logic*_gen.py- Language-specific code generatorsboilerplate/- Runtime libraries for each language
examples/- Example .proto files and usage demosmain.c- C API demonstration (encoding/decoding, parsing)index.ts- TypeScript API demonstration (similar functionality)*.proto- Protocol Buffer definitions for examples
generated/- Output directory for generated code (git-ignored)
| Type | Size (bytes) | Description | Range/Notes |
|---|---|---|---|
| Integers | |||
int8 |
1 | Signed 8-bit integer | -128 to 127 |
uint8 |
1 | Unsigned 8-bit integer | 0 to 255 |
int16 |
2 | Signed 16-bit integer | -32,768 to 32,767 |
uint16 |
2 | Unsigned 16-bit integer | 0 to 65,535 |
int32 |
4 | Signed 32-bit integer | -2.1B to 2.1B |
uint32 |
4 | Unsigned 32-bit integer | 0 to 4.3B |
int64 |
8 | Signed 64-bit integer | Large integers |
uint64 |
8 | Unsigned 64-bit integer | Large positive integers |
| Floating Point | |||
float |
4 | Single precision (IEEE 754) | 7 decimal digits |
double |
8 | Double precision (IEEE 754) | 15-17 decimal digits |
| Other | |||
bool |
1 | Boolean value | true or false |
string |
Variable | UTF-8 encoded string | Length-prefixed |
EnumType |
1 | Custom enumeration | Defined in .proto |
MessageType |
Variable | Nested message | User-defined structure |
Note: All types use little-endian byte order for cross-platform compatibility.
Arrays (repeated fields) support all data types - primitives, enums, and messages across all target languages.
| Array Type | Syntax | Memory Usage | Use Case |
|---|---|---|---|
| Fixed | repeated type field = N [size=X]; |
sizeof(type) * X |
Matrices, buffers (always full) |
| Bounded | repeated type field = N [max_size=X]; |
1 byte (count) + sizeof(type) * X |
Dynamic lists with limits |
| String Arrays | repeated string field = N [max_size=X, element_size=Y]; |
1 byte (count) + X * Y bytes |
Text collections with size limits |
message ArrayExample {
repeated float matrix = 1 [size=9]; // 3x3 matrix (always 9 elements)
repeated string names = 2 [max_size=10, element_size=32]; // Up to 10 strings, each max 32 chars
repeated int32 values = 3 [max_size=100]; // Up to 100 integers (variable count)
}Generated Output (all languages now supported):
- Python:
matrix: list[float],names: list[str],values: list[int] - C:
float matrix[9],struct { uint8_t count; char data[10][32]; } names - C++:
float matrix[9],struct { uint8_t count; char data[10][32]; } names(with enum classes) - TypeScript:
Array('matrix', 'Float32LE', 9),Array('names_data', 'String', 10) - GraphQL:
matrix: [Float!]!,names: [String!]!,values: [Int!]!
Important: String arrays require both
max_size(orsize) ANDelement_sizeparameters because they are "arrays of arrays" - you need to specify both how many strings AND the maximum size of each individual string. This ensures predictable memory layout and prevents buffer overflows.
Strings are a special case of bounded character arrays with built-in UTF-8 encoding and null-termination handling across all target languages.
| String Type | Syntax | Memory Usage | Use Case |
|---|---|---|---|
| Fixed String | string field = N [size=X]; |
X bytes |
Fixed-width text fields |
| Variable String | string field = N [max_size=X]; |
1 byte (length) + X bytes |
Text with known maximum length |
message StringExample {
string device_name = 1 [size=16]; // Exactly 16 characters (pad with nulls)
string description = 2 [max_size=256]; // Up to 256 characters (length-prefixed)
string error_msg = 3 [max_size=128]; // Up to 128 characters for error messages
}String Features:
- Simplified Schema: No need to specify
repeated uint8for text data - Automatic Encoding: UTF-8 encoding/decoding handled by generators
- Null Handling: Proper null-termination and padding for fixed strings
- Type Safety: Clear distinction between binary data and text
- Cross-Language: Consistent string handling across C, TypeScript, and Python
Message ID (msgid) - Required for serializable messages:
message MyMessage {
option msgid = 42; // Must be unique within package (0-65535)
string content = 1;
}Flatten (flatten=true) - Merge nested message fields into parent:
message Position {
double lat = 1;
double lon = 2;
}
message Status {
Position pos = 1 [flatten=true]; // lat, lon become direct fields
float battery = 2;
}Array Options - Control array behavior:
message Data {
repeated int32 fixed_buffer = 1 [size=256]; // Always 256 integers
repeated int32 var_buffer = 2 [max_size=256]; // Up to 256 integers
repeated string messages = 3 [max_size=10, element_size=64]; // Up to 10 strings, each max 64 chars
string device_name = 4 [size=32]; // Always 32 characters
string description = 5 [max_size=256]; // Up to 256 characters
}package sensor_system;
enum SensorType {
TEMPERATURE = 0;
HUMIDITY = 1;
PRESSURE = 2;
}
message Position {
double lat = 1;
double lon = 2;
float alt = 3;
}
message SensorReading {
option msgid = 1;
uint32 device_id = 1;
int64 timestamp = 2;
SensorType type = 3;
// Device name (fixed 16-character string)
string device_name = 4 [size=16];
// Sensor location (flattened)
Position location = 5 [flatten=true];
// Measurement values (up to 8 readings)
repeated float values = 6 [max_size=8];
// Calibration matrix (always 3x3 = 9 elements)
repeated float calibration = 7 [size=9];
// Error message (up to 128 characters)
string error_msg = 8 [max_size=128];
bool valid = 9;
}
message DeviceStatus {
option msgid = 2;
uint32 device_id = 1;
repeated SensorReading recent_readings = 2 [max_size=10];
float battery_level = 3;
}- Message IDs: Must be unique within package (0-65535)
- Field numbers: Must be unique within message
- Array requirements: All
repeatedfields must specify[size=X](fixed) or[max_size=X](bounded) - String requirements: All
stringfields must specify[size=X](fixed) or[max_size=X](variable) - String array requirements:
repeated stringfields must specify both array size AND[element_size=Y] - Flatten constraints: No field name collisions after flattening
- Size limits: Arrays limited to 255 elements maximum
# Generate all languages
python -m struct_frame schema.proto --build_c --build_cpp --build_ts --build_py --build_gql
# Language-specific paths
python -m struct_frame schema.proto --build_py --py_path output/python/
python -m struct_frame schema.proto --build_c --c_path output/c/
python -m struct_frame schema.proto --build_cpp --cpp_path output/cpp/
python -m struct_frame schema.proto --build_ts --ts_path output/typescript/
python -m struct_frame schema.proto --build_gql --gql_path output/graphql/The C++ implementation provides modern C++ features while maintaining compatibility with the same binary message formats used by the C implementation:
- Enum Classes: Enums are generated as strongly-typed
enum classtypes instead of plain enums - Modern C++ Style: Uses classes, namespaces, templates, and RAII patterns
- Binary Compatibility: Generated structs use the same memory layout as C (via
__attribute__((packed))) - Type Safety: Leverages C++ templates for type-safe message helpers
- STL Integration: Uses standard library features like
<cstdint>,<functional>, and<span>
package robot;
enum RobotStatus : uint8_t {
IDLE = 0;
MOVING = 1;
ERROR = 2;
}
message RobotState {
option msgid = 10;
uint32 robot_id = 1;
RobotStatus status = 2;
float battery_level = 3;
}Generated C++ Code:
// Enum class instead of plain enum
enum class RobotRobotStatus : uint8_t {
IDLE = 0,
MOVING = 1,
ERROR = 2
};
// Packed struct compatible with C
struct RobotRobotState {
uint32_t robot_id;
RobotRobotStatus status;
float battery_level;
} __attribute__((packed));
constexpr size_t ROBOT_ROBOT_STATE_MAX_SIZE = 9;
constexpr size_t ROBOT_ROBOT_STATE_MSG_ID = 10;
// Helper function in namespace
namespace StructFrame {
inline bool get_message_length(size_t msg_id, size_t* size) {
switch (msg_id) {
case ROBOT_ROBOT_STATE_MSG_ID:
*size = ROBOT_ROBOT_STATE_MAX_SIZE;
return true;
default: break;
}
return false;
}
} // namespace StructFrameUsage:
#include "robot.sf.hpp"
#include "struct_frame.hpp"
// Create and initialize message
RobotRobotState state{};
state.robot_id = 123;
state.status = RobotRobotStatus::MOVING; // Type-safe enum class
state.battery_level = 85.5f;
// Encode with modern C++ API
uint8_t buffer[256];
StructFrame::BasicPacket format;
StructFrame::EncodeBuffer encoder(buffer, sizeof(buffer));
if (encoder.encode(&format, ROBOT_ROBOT_STATE_MSG_ID, &state, sizeof(state))) {
// Frame encoded successfully
std::cout << "Encoded " << encoder.size() << " bytes\n";
}
// Parse with lambda callback
StructFrame::FrameParser parser(&format, [](size_t msg_id, size_t* size) {
return StructFrame::get_message_length(msg_id, size);
});
// Process received bytes
for (size_t i = 0; i < encoder.size(); i++) {
auto info = parser.parse_byte(buffer[i]);
if (info.valid) {
auto* msg = reinterpret_cast<RobotRobotState*>(info.msg_location);
std::cout << "Robot ID: " << msg->robot_id
<< ", Status: " << static_cast<int>(msg->status) << "\n";
}
}An experimental Wireshark Lua dissector is available for protocol analysis and debugging of struct-frame packets. See wireshark/README.md for installation and usage instructions.
- Array Implementation Guide - Documentation of array features, syntax, and generated code examples across all languages