Raise host frame payload limit so multi-client UART replies fit.

Use a uint16 LE length and 1024-byte max payload so CLIENT_INFO and BATTERY_STATUS no longer fail encoding past ~10 connected pods.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-01 23:43:33 +01:00
co-authored by Cursor
parent 73f48c8002
commit 9046677460
11 changed files with 72 additions and 39 deletions
+2 -2
View File
@@ -225,12 +225,12 @@ Unabhängig von Protobuf — reines Bytestream-Framing:
| Feld | Wert | | Feld | Wert |
|------|------| |------|------|
| Start | `0xAA` | | Start | `0xAA` |
| Länge | 1 Byte (1–252) | | Länge | 2 Bytes little-endian (1–1024) |
| Payload | `length` Bytes | | Payload | `length` Bytes |
| Prüfsumme | XOR aller Payload-Bytes | | Prüfsumme | XOR aller Payload-Bytes |
| Stopp | `0xCC` | | Stopp | `0xCC` |
**Parameter:** `main/uart.h` — `UART_NUM_1`, `921600` Baud, TX GPIO **2**, RX GPIO **3**, `MAX_PAYLOAD_SIZE` 248. **Parameter:** `main/uart.h` — `UART_NUM_1`, `921600` Baud, TX GPIO **2**, RX GPIO **3**, `MAX_PAYLOAD_SIZE` 1024.
### 5.2 Nutzlast (Anwendung) ### 5.2 Nutzlast (Anwendung)
+4 -4
View File
@@ -124,10 +124,10 @@ idf.py -p /dev/ttyUSB0 flash monitor
| Byte | Inhalt | | Byte | Inhalt |
|------|--------| |------|--------|
| 0 | `0xAA` Start | | 0 | `0xAA` Start |
| 1 | Länge N (1–252) | | 1–2 | Länge N little-endian (1–1024) |
| 2…N+1 | Payload | | 3…N+2 | Payload |
| N+2 | XOR-Checksum über Payload | | N+3 | XOR-Checksum über Payload |
| N+3 | `0xCC` Stopp | | N+4 | `0xCC` Stopp |
Implementierung: `uart.c` — `parse_uart_byte()`, `uart_send_framed()`. Implementierung: `uart.c` — `parse_uart_byte()`, `uart_send_framed()`.
+1 -1
View File
@@ -287,7 +287,7 @@ Entspricht `main/README.md`:
| Byte | Inhalt | | Byte | Inhalt |
|------|--------| |------|--------|
| Frame | `0xAA`, Länge, Payload, XOR-Checksum, `0xCC` | | Frame | `0xAA`, Länge (uint16 LE), Payload, XOR-Checksum, `0xCC` |
| Payload[0] | `MessageType` (z.B. `22` = FIND_ME) | | Payload[0] | `MessageType` (z.B. `22` = FIND_ME) |
| Payload[1…] | Nanopb-`UartMessage` | | Payload[1…] | Nanopb-`UartMessage` |
+21 -11
View File
@@ -10,8 +10,10 @@ import (
const ( const (
StartMarker = 0xAA StartMarker = 0xAA
StopMarker = 0xCC StopMarker = 0xCC
// Must match main/uart.h MAX_PAYLOAD_SIZE (MAX_BUF_SIZE - 4). // Must match main/uart.h MAX_PAYLOAD_SIZE.
MaxPayload = 248 MaxPayload = 1024
// AA | len_lo | len_hi | payload | xor | CC
FrameOverhead = 5
) )
var ( var (
@@ -19,14 +21,15 @@ var (
ErrTimeout = errors.New("read timeout") ErrTimeout = errors.New("read timeout")
) )
// EncodeFrame builds a framed packet: 0xAA len payload xor(payload) 0xCC. // EncodeFrame builds a framed packet: 0xAA len_lo len_hi payload xor(payload) 0xCC.
func EncodeFrame(payload []byte) ([]byte, error) { func EncodeFrame(payload []byte) ([]byte, error) {
if len(payload) == 0 || len(payload) > MaxPayload { if len(payload) == 0 || len(payload) > MaxPayload {
return nil, fmt.Errorf("payload length %d out of range 1..%d", len(payload), MaxPayload) return nil, fmt.Errorf("payload length %d out of range 1..%d", len(payload), MaxPayload)
} }
frame := make([]byte, 0, 4+len(payload)) n := len(payload)
frame = append(frame, StartMarker, byte(len(payload))) frame := make([]byte, 0, FrameOverhead+n)
frame = append(frame, StartMarker, byte(n&0xff), byte((n>>8)&0xff))
var checksum byte var checksum byte
for _, b := range payload { for _, b := range payload {
frame = append(frame, b) frame = append(frame, b)
@@ -39,6 +42,7 @@ func EncodeFrame(payload []byte) ([]byte, error) {
type Parser struct { type Parser struct {
state int state int
len int len int
lenLo byte
payload []byte payload []byte
index int index int
checksum byte checksum byte
@@ -46,7 +50,8 @@ type Parser struct {
const ( const (
stateStart = iota stateStart = iota
stateLen stateLenLo
stateLenHi
stateData stateData
stateChecksum stateChecksum
stateStop stateStop
@@ -63,14 +68,19 @@ func (p *Parser) Feed(b byte) (payload []byte, ok bool, err error) {
if b == StartMarker { if b == StartMarker {
p.index = 0 p.index = 0
p.checksum = 0 p.checksum = 0
p.state = stateLen p.state = stateLenLo
} }
case stateLen: case stateLenLo:
if b == 0 || b > MaxPayload { p.lenLo = b
p.state = stateLenHi
case stateLenHi:
n := int(p.lenLo) | (int(b) << 8)
if n == 0 || n > MaxPayload {
p.state = stateStart p.state = stateStart
} else { } else {
p.len = int(b) p.len = n
p.payload = make([]byte, p.len) p.payload = make([]byte, p.len)
p.index = 0
p.state = stateData p.state = stateData
} }
case stateData: case stateData:
@@ -102,7 +112,7 @@ func (p *Parser) Feed(b byte) (payload []byte, ok bool, err error) {
// maxWait bounds total wait time; zero means no limit (serial read timeouts retry forever). // maxWait bounds total wait time; zero means no limit (serial read timeouts retry forever).
func ReadFrame(r io.Reader, buf []byte, maxWait time.Duration) ([]byte, error) { func ReadFrame(r io.Reader, buf []byte, maxWait time.Duration) ([]byte, error) {
if buf == nil { if buf == nil {
buf = make([]byte, 256) buf = make([]byte, 1024)
} }
parser := NewParser() parser := NewParser()
+2 -2
View File
@@ -166,7 +166,7 @@ Hardware: **UART1**, **921600** baud, **TX = GPIO3**, **RX = GPIO2** (adapter TX
| Field | Value | | Field | Value |
|-------|--------| |-------|--------|
| Start | `0xAA` | | Start | `0xAA` |
| Length | 1 byte, payload size 1–252 | | Length | 2 bytes little-endian, payload size 1–1024 |
| Payload | `length` bytes | | Payload | `length` bytes |
| Checksum | XOR of all payload bytes | | Checksum | XOR of all payload bytes |
| Stop | `0xCC` | | Stop | `0xCC` |
@@ -178,7 +178,7 @@ Hardware: **UART1**, **921600** baud, **TX = GPIO3**, **RX = GPIO2** (adapter TX
| 0 | Command ID (`MessageType` / `msg_id`) | | 0 | Command ID (`MessageType` / `msg_id`) |
| 1… | Arguments (handler receives bytes after ID only) | | 1… | Arguments (handler receives bytes after ID only) |
Example VERSION request: single-byte payload `03` → frame `AA 01 03 03 CC`. Example VERSION request: single-byte payload `03` → frame `AA 01 00 03 03 CC`.
Logging: Logging:
+3 -3
View File
@@ -113,8 +113,8 @@ static esp_err_t send_tick_truncated(alox_UartMessage *msg) {
size_t payload_len = stream.bytes_written + 1; size_t payload_len = stream.bytes_written + 1;
uint16_t max_pay = ble_host_max_payload(); uint16_t max_pay = ble_host_max_payload();
/* Framed size = payload + 4; notify must fit ATT MTU-3. */ /* Framed size = payload + UART_FRAME_OVERHEAD; notify must fit ATT MTU-3. */
size_t frame_len = payload_len + 4; size_t frame_len = payload_len + UART_FRAME_OVERHEAD;
if (payload_len <= MAX_PAYLOAD_SIZE && frame_len <= max_pay) { if (payload_len <= MAX_PAYLOAD_SIZE && frame_len <= max_pay) {
uint8_t payload[MAX_PAYLOAD_SIZE]; uint8_t payload[MAX_PAYLOAD_SIZE];
payload[0] = (uint8_t)msg->type; payload[0] = (uint8_t)msg->type;
@@ -197,7 +197,7 @@ void ble_stream_update(void) {
if (want && !s_running) { if (want && !s_running) {
s_running = true; s_running = true;
if (xTaskCreate(ble_stream_task, "ble_stream", 4096, NULL, 5, &s_task) != if (xTaskCreate(ble_stream_task, "ble_stream", 8192, NULL, 5, &s_task) !=
pdPASS) { pdPASS) {
s_running = false; s_running = false;
s_task = NULL; s_task = NULL;
+5 -4
View File
@@ -22,14 +22,14 @@ void host_transport_set_cmd_queue(QueueHandle_t cmd_queue) {
QueueHandle_t host_transport_cmd_queue(void) { return s_cmd_queue; } QueueHandle_t host_transport_cmd_queue(void) { return s_cmd_queue; }
bool host_enqueue_packet(const uint8_t *payload, uint8_t len) { bool host_enqueue_packet(const uint8_t *payload, size_t len) {
if (s_cmd_queue == NULL || payload == NULL || len == 0) { if (s_cmd_queue == NULL || payload == NULL || len == 0) {
return false; return false;
} }
generic_msg_t msg = { generic_msg_t msg = {
.msg_id = payload[0], .msg_id = payload[0],
.len = len > 1 ? (size_t)(len - 1) : 0, .len = len > 1 ? (len - 1) : 0,
.payload = NULL, .payload = NULL,
}; };
@@ -63,10 +63,11 @@ esp_err_t host_send_framed(const uint8_t *payload, size_t len) {
checksum ^= payload[i]; checksum ^= payload[i];
} }
uint8_t frame[4 + MAX_PAYLOAD_SIZE]; uint8_t frame[UART_FRAME_OVERHEAD + MAX_PAYLOAD_SIZE];
size_t pos = 0; size_t pos = 0;
frame[pos++] = START_MARKER; frame[pos++] = START_MARKER;
frame[pos++] = (uint8_t)len; frame[pos++] = (uint8_t)(len & 0xff);
frame[pos++] = (uint8_t)((len >> 8) & 0xff);
memcpy(&frame[pos], payload, len); memcpy(&frame[pos], payload, len);
pos += len; pos += len;
frame[pos++] = checksum; frame[pos++] = checksum;
+2 -2
View File
@@ -19,7 +19,7 @@ void host_transport_set_cmd_queue(QueueHandle_t cmd_queue);
QueueHandle_t host_transport_cmd_queue(void); QueueHandle_t host_transport_cmd_queue(void);
/** /**
* Build AA|len|payload|xor|CC and send via the registered TX backend. * Build AA|len_lo|len_hi|payload|xor|CC and send via the registered TX backend.
* Same semantics as uart_send_framed. * Same semantics as uart_send_framed.
*/ */
esp_err_t host_send_framed(const uint8_t *payload, size_t len); esp_err_t host_send_framed(const uint8_t *payload, size_t len);
@@ -31,6 +31,6 @@ esp_err_t host_send_framed(const uint8_t *payload, size_t len);
void host_rx_bytes(const uint8_t *data, size_t len); void host_rx_bytes(const uint8_t *data, size_t len);
/** Enqueue a completed uart_packet_t (payload[0]=msg type). */ /** Enqueue a completed uart_packet_t (payload[0]=msg type). */
bool host_enqueue_packet(const uint8_t *payload, uint8_t len); bool host_enqueue_packet(const uint8_t *payload, size_t len);
#endif #endif
+13 -4
View File
@@ -81,18 +81,27 @@ bool parse_uart_byte(uint8_t byte, uart_packet_t *p) {
if (byte == START_MARKER) { if (byte == START_MARKER) {
p->index = 0; p->index = 0;
p->checksum = 0; p->checksum = 0;
p->state = STATE_LEN; p->len = 0;
p->state = STATE_LEN_LO;
} }
break; break;
case STATE_LEN: case STATE_LEN_LO:
if (byte > MAX_PAYLOAD_SIZE || byte == 0) { p->len = byte;
p->state = STATE_LEN_HI;
break;
case STATE_LEN_HI: {
uint16_t len = (uint16_t)(p->len | ((uint16_t)byte << 8));
if (len == 0 || len > MAX_PAYLOAD_SIZE) {
p->state = STATE_START; p->state = STATE_START;
} else { } else {
p->len = byte; p->len = len;
p->index = 0;
p->state = STATE_DATA; p->state = STATE_DATA;
} }
break; break;
}
case STATE_DATA: case STATE_DATA:
p->payload[p->index++] = byte; p->payload[p->index++] = byte;
+11 -6
View File
@@ -22,13 +22,18 @@
#define UART_DRIVER_TX_BUF_SIZE 4096 #define UART_DRIVER_TX_BUF_SIZE 4096
#define START_MARKER 0xAA #define START_MARKER 0xAA
#define STOP_MARKER 0xCC #define STOP_MARKER 0xCC
#define MAX_BUF_SIZE 252 /** Frame: AA | len_lo | len_hi | payload[len] | xor | CC */
#define MAX_PAYLOAD_SIZE \ #define UART_FRAME_OVERHEAD 5
MAX_BUF_SIZE - 4 // Buffer overhead, Start, Len, CRC, End /**
* Max application payload (type byte + protobuf). Sized for CLIENT_REGISTRY_MAX
* aggregations (CLIENT_INFO / BATTERY_STATUS with 16+ samples).
*/
#define MAX_PAYLOAD_SIZE 1024
typedef enum { typedef enum {
STATE_START, STATE_START,
STATE_LEN, STATE_LEN_LO,
STATE_LEN_HI,
STATE_DATA, STATE_DATA,
STATE_CHECKSUM, STATE_CHECKSUM,
STATE_STOP STATE_STOP
@@ -36,8 +41,8 @@ typedef enum {
typedef struct { typedef struct {
uint8_t payload[MAX_PAYLOAD_SIZE]; uint8_t payload[MAX_PAYLOAD_SIZE];
uint8_t len; uint16_t len;
uint8_t index; uint16_t index;
uint8_t checksum; uint8_t checksum;
parser_state_t state; parser_state_t state;
} uart_packet_t; } uart_packet_t;
+8
View File
@@ -1,18 +1,26 @@
#include "uart_proto.h" #include "uart_proto.h"
#include "esp_log.h"
#include "pb_encode.h" #include "pb_encode.h"
#include "uart.h" #include "uart.h"
#include <string.h> #include <string.h>
static const char *TAG = "[UART_PROTO]";
esp_err_t uart_send_uart_message(const alox_UartMessage *msg) { esp_err_t uart_send_uart_message(const alox_UartMessage *msg) {
uint8_t pb_buf[MAX_PAYLOAD_SIZE]; uint8_t pb_buf[MAX_PAYLOAD_SIZE];
pb_ostream_t stream = pb_ostream_from_buffer(pb_buf, sizeof(pb_buf)); pb_ostream_t stream = pb_ostream_from_buffer(pb_buf, sizeof(pb_buf));
if (!pb_encode(&stream, alox_UartMessage_fields, msg)) { if (!pb_encode(&stream, alox_UartMessage_fields, msg)) {
ESP_LOGE(TAG, "pb_encode failed type=%u (%s)", (unsigned)msg->type,
PB_GET_ERROR(&stream));
return ESP_FAIL; return ESP_FAIL;
} }
uint8_t payload[MAX_PAYLOAD_SIZE]; uint8_t payload[MAX_PAYLOAD_SIZE];
if (stream.bytes_written + 1 > sizeof(payload)) { if (stream.bytes_written + 1 > sizeof(payload)) {
ESP_LOGE(TAG, "payload too large type=%u need=%u max=%u",
(unsigned)msg->type, (unsigned)(stream.bytes_written + 1),
(unsigned)sizeof(payload));
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
} }