Minimal Frame Parsing Guide¶
Overview¶
Minimal frames (PAYLOAD_MINIMAL) are the most lightweight framing option in struct-frame, using the format [MSG_ID] [PACKET] with no length field and no CRC checksum. They're ideal for:
- Bandwidth-constrained links (LoRa, radio, RF)
- Trusted communication (SPI, I2C, shared memory)
- Fixed-size messages (sensor readings, control commands)
- Low-power applications (battery-powered sensors)
Frame Structure¶
Minimal frames have three variants based on the header type:
BasicMinimal¶
[0x90] [0x70] [MSG_ID] [PAYLOAD]
▲ ▲ ▲ ▲
│ │ │ └─ Your message data
│ │ └─ Message ID (1 byte)
│ └─ Payload type indicator (Minimal = 0x70)
└─ Basic frame marker
Overhead: 3 bytes
TinyMinimal¶
[0x70] [MSG_ID] [PAYLOAD]
▲ ▲ ▲
│ │ └─ Your message data
│ └─ Message ID (1 byte)
└─ Tiny+Minimal marker (0x70 = 0x70+0)
Overhead: 2 bytes
NoneMinimal¶
Key Requirement: Message Length Callback¶
Since minimal frames don't include a length field, you must provide a callback function that returns the expected message length for each message ID. Without this callback, the parser cannot determine where one message ends and the next begins.
Language-Specific Examples¶
Python¶
Note: The get_msg_length function is automatically generated by struct-frame based on your message definitions.
from parser import Parser, HeaderType, PayloadType
# Import the auto-generated get_msg_length function
from my_messages_sf import get_msg_length
# Create parser with auto-generated callback
parser = Parser(
get_msg_length=get_msg_length,
enabled_headers=[HeaderType.BASIC, HeaderType.TINY],
enabled_payloads=[PayloadType.MINIMAL]
)
# Parse incoming bytes
for byte in incoming_data:
result = parser.parse_byte(byte)
if result.valid:
print(f"Received msg_id={result.msg_id}, data={result.msg_data.hex()}")
# Encode a minimal frame
frame = parser.encode(
msg_id=1,
msg=b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A',
header_type=HeaderType.TINY,
payload_type=PayloadType.MINIMAL
)
# Result: [0x70] [0x01] [0x01 0x02 ... 0x0A]
Manual callback (if needed): If you need custom logic, you can still define your own callback:
def get_msg_length(msg_id: int) -> int:
"""Return message length for each msg_id"""
message_sizes = {
1: 10, # Status message is 10 bytes
2: 20, # Command message is 20 bytes
3: 5, # Sensor reading is 5 bytes
}
return message_sizes.get(msg_id, 0)
TypeScript¶
Note: The get_message_length function is automatically generated by struct-frame.
import { GenericFrameParser, FrameParserConfig, PayloadType } from './frame_base';
import { HEADER_TINY_CONFIG } from './frame_headers/header_tiny';
import { PAYLOAD_MINIMAL_CONFIG } from './payload_types/payload_minimal';
// Import the auto-generated function
import { get_message_length } from './my_messages.sf';
// Configure for TinyMinimal
const config: FrameParserConfig = {
name: 'TinyMinimal',
startBytes: [0x70], // Tiny+Minimal
headerSize: 2, // start byte + msg_id
footerSize: 0, // no CRC
hasLength: false, // no length field
lengthBytes: 0,
hasCrc: false,
};
// Create parser with auto-generated callback
const parser = new GenericFrameParser(config, get_message_length);
// Parse incoming bytes
for (const byte of incomingData) {
const result = parser.parse_byte(byte);
if (result.valid) {
console.log(`Received msg_id=${result.msg_id}, data=${result.msg_data}`);
}
}
// Encode a minimal frame
const frame = parser.encode(1, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
// Result: [0x70] [0x01] [0x01 0x02 ... 0x0A]
C++¶
Note: The actual generated parser API may vary. This example shows the general pattern. Consult the generated code for your specific parser class constructor signature.
#include "frame_parsers.hpp"
using namespace FrameParsers;
// Define message size lookup
bool get_msg_length(uint8_t msg_id, size_t* length) {
switch (msg_id) {
case 1: *length = 10; return true; // Status message
case 2: *length = 20; return true; // Command message
case 3: *length = 5; return true; // Sensor reading
default: return false;
}
}
// Create buffer for parser
uint8_t buffer[256];
// Example pattern - actual API depends on generated code
// The parser may be created with a callback parameter
// or may have a separate method to set the callback
auto parser = TinyMinimalParser(buffer, sizeof(buffer), get_msg_length);
// Parse incoming bytes
for (uint8_t byte : incoming_data) {
FrameMsgInfo result = parser.parse_byte(byte);
if (result.valid) {
printf("Received msg_id=%d, len=%zu\n", result.msg_id, result.msg_len);
// Process result.msg_data
}
}
// Encode a minimal frame
uint8_t msg_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
size_t frame_len = parser.encode(1, msg_data, 10, buffer, sizeof(buffer));
// Result in buffer: [0x70] [0x01] [0x01 0x02 ... 0x0A]
C¶
#include "frame_parsers.h"
// Define message size lookup callback
bool get_msg_length(uint8_t msg_id, size_t* length) {
switch (msg_id) {
case 1: *length = 10; return true; // Status message
case 2: *length = 20; return true; // Command message
case 3: *length = 5; return true; // Sensor reading
default: return false;
}
}
// Create parser instance
uint8_t buffer[256];
// Note: Specific parser struct would be used
// This shows the pattern
// Parse incoming bytes
for (size_t i = 0; i < data_len; i++) {
frame_msg_info_t result = parser_parse_byte(&parser, data[i]);
if (result.valid) {
printf("Received msg_id=%d, len=%zu\n", result.msg_id, result.msg_len);
// Process result.msg_data
}
}
// Encode a minimal frame
uint8_t msg_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
uint8_t output[256];
size_t frame_len = encode_tiny_minimal(1, msg_data, 10, output, sizeof(output));
// Result in output: [0x70] [0x01] [0x01 0x02 ... 0x0A]
C¶
using StructFrame;
// Define message size lookup
int? GetMsgLength(int msgId)
{
return msgId switch
{
1 => 10, // Status message is 10 bytes
2 => 20, // Command message is 20 bytes
3 => 5, // Sensor reading is 5 bytes
_ => null
};
}
// Create parser with callback
var parser = new TinyMinimalParser(GetMsgLength);
// Parse incoming bytes
foreach (byte b in incomingData)
{
var result = parser.ParseByte(b);
if (result.Valid)
{
Console.WriteLine($"Received msg_id={result.MsgId}, data={BitConverter.ToString(result.MsgData)}");
}
}
// Encode a minimal frame
byte[] msgData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
byte[] frame = parser.Encode(1, msgData);
// Result: [0x70] [0x01] [0x01 0x02 ... 0x0A]
Common Patterns¶
Important: Struct-frame automatically generates the get_msg_length (Python) or get_message_length (TypeScript/C++/C#/C) function for you based on your message definitions. You typically don't need to write your own callback.
The auto-generated function looks up message sizes from your proto definitions. For example, if you have:
The generator will automatically include this message size in the lookup function.
Manual patterns (for advanced use cases only):
Pattern 1: Fixed-Size Messages¶
When all your messages are the same size:
Pattern 2: Size Lookup Table¶
When you have multiple message types:
MESSAGE_SIZES = {
1: 10,
2: 20,
3: 5,
4: 100,
}
def get_msg_length(msg_id: int) -> int:
return MESSAGE_SIZES.get(msg_id, 0)
Pattern 3: Generated from Proto¶
Automatically derive sizes from your message definitions:
from my_messages import StatusMessage, CommandMessage, SensorReading
MESSAGE_SIZES = {
StatusMessage.msg_id: StatusMessage.msg_size,
CommandMessage.msg_id: CommandMessage.msg_size,
SensorReading.msg_id: SensorReading.msg_size,
}
def get_msg_length(msg_id: int) -> int:
return MESSAGE_SIZES.get(msg_id, 0)
Choosing a Header Type¶
BasicMinimal (3-byte overhead)¶
- Use when: You want robust framing with clear start markers
- Best for: Serial ports, UART, general-purpose links
- Example:
[0x90] [0x70] [MSG_ID] [DATA]
TinyMinimal (2-byte overhead)¶
- Use when: Every byte counts but you still want start markers
- Best for: Low-power sensors, LoRa, RF links
- Example:
[0x70] [MSG_ID] [DATA]
NoneMinimal (1-byte overhead)¶
- Use when: You have external synchronization or trust the link
- Best for: SPI, I2C, shared memory, trusted IPC
- Example:
[MSG_ID] [DATA]
Pros and Cons¶
Advantages¶
✅ Minimal overhead: 1-3 bytes total ✅ Fast encoding: No CRC calculation ✅ Fast decoding: No CRC validation ✅ Simple: Easy to understand and debug ✅ Efficient: Perfect for fixed-size messages
Disadvantages¶
❌ No error detection: Corrupted data won't be detected ❌ Requires callback: Must provide message size lookup ❌ Less flexible: All messages of same ID must be same size ❌ Trusted links only: Use on reliable or error-corrected links
When to Use Minimal Frames¶
✅ Good Use Cases¶
- Sensor networks: Temperature, humidity, pressure readings with fixed sizes
- Control systems: Fixed-size command/response protocols
- Embedded IPC: Board-to-board communication on PCB
- LoRa/RF: Bandwidth is precious, messages are small and fixed
- Real-time systems: Need lowest latency, have reliable links
❌ Avoid When¶
- Unreliable links: Use
PAYLOAD_DEFAULTwith CRC instead - Variable-size messages: Use
PAYLOAD_DEFAULTwith length field - Unknown message sizes: Can't use minimal without size lookup
- Error-prone environment: Need CRC validation
Upgrading to Default Payload¶
If you later need error detection or variable lengths:
# From Minimal
parser = Parser(
get_msg_length=get_msg_length,
enabled_payloads=[PayloadType.MINIMAL]
)
# To Default (adds length + CRC)
parser = Parser(
enabled_payloads=[PayloadType.DEFAULT]
)
# No callback needed - length is in frame
# CRC provides error detection
Best Practices¶
- Document your message sizes: Keep a clear mapping of msg_id → size
- Validate on sender side: Check message is expected size before sending
- Use on trusted links: Don't use minimal on noisy serial links
- Consider upgrading: If reliability issues arise, upgrade to
PAYLOAD_DEFAULT - Test thoroughly: Without CRC, bugs can be harder to detect
Performance Comparison¶
| Payload Type | Overhead | CRC | Length | Parsing Speed | Best For |
|---|---|---|---|---|---|
| Minimal | 1-3 bytes | ❌ | ❌ | ⚡ Fastest | Fixed-size, trusted links |
| Default | 5-6 bytes | ✅ | ✅ | 🚀 Fast | General purpose |
| Extended | 8+ bytes | ✅ | ✅ | 🚀 Fast | Large payloads, namespaces |
See Also¶
- Framing Guide - Choosing the right profile
- Parser Feature Matrix - Language capabilities
- Python SDK - Python-specific parser API
- C++ SDK - C++ parser API
- TypeScript SDK - TypeScript parser API