File syn_mbus.c¶
File List > proto > syn_mbus.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_MBUS) || SYN_USE_MBUS
#include "../util/syn_assert.h"
#include "syn_mbus.h"
#include <string.h>
/* ── Decoder state machine internal states ───────────────────────────────── */
enum {
STATE_IDLE = 0,
STATE_SHORT_C,
STATE_SHORT_A,
STATE_SHORT_CHK,
STATE_SHORT_STOP,
STATE_LONG_L1,
STATE_LONG_L2,
STATE_LONG_SOF2,
STATE_LONG_BODY,
};
/* ── Checksum Calculation ───────────────────────────────────────────────── */
uint8_t syn_mbus_calc_checksum(const uint8_t *data, size_t len)
{
if (data == NULL || len == 0) {
return 0;
}
uint8_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum = (uint8_t)(sum + data[i]);
}
return sum;
}
/* ── One-Shot Encoders ───────────────────────────────────────────────────── */
SYN_Status syn_mbus_encode_ack(uint8_t *buf, size_t cap, size_t *out_len)
{
if (buf == NULL || out_len == NULL) {
return SYN_INVALID_PARAM;
}
if (cap < 1) {
return SYN_ERROR;
}
buf[0] = SYN_MBUS_ACK_BYTE;
*out_len = 1;
return SYN_OK;
}
SYN_Status syn_mbus_encode_short(uint8_t c_field, uint8_t a_field, uint8_t *buf, size_t cap,
size_t *out_len)
{
if (buf == NULL || out_len == NULL) {
return SYN_INVALID_PARAM;
}
if (cap < 5) {
return SYN_ERROR;
}
buf[0] = SYN_MBUS_START_SHORT;
buf[1] = c_field;
buf[2] = a_field;
buf[3] = (uint8_t)(c_field + a_field);
buf[4] = SYN_MBUS_STOP;
*out_len = 5;
return SYN_OK;
}
SYN_Status syn_mbus_encode_control(uint8_t c_field, uint8_t a_field, uint8_t ci_field, uint8_t *buf,
size_t cap, size_t *out_len)
{
return syn_mbus_encode_long(c_field, a_field, ci_field, NULL, 0, buf, cap, out_len);
}
SYN_Status syn_mbus_encode_long(uint8_t c_field, uint8_t a_field, uint8_t ci_field,
const uint8_t *payload, uint8_t payload_len, uint8_t *buf,
size_t cap, size_t *out_len)
{
if (buf == NULL || out_len == NULL) {
return SYN_INVALID_PARAM;
}
if (payload == NULL && payload_len > 0) {
return SYN_INVALID_PARAM;
}
if (payload_len > SYN_MBUS_MAX_PAYLOAD) {
return SYN_INVALID_PARAM;
}
size_t total_len = (size_t)payload_len + 9u;
if (cap < total_len) {
return SYN_ERROR;
}
uint8_t l_field = (uint8_t)(payload_len + 3u);
buf[0] = SYN_MBUS_START_LONG;
buf[1] = l_field;
buf[2] = l_field;
buf[3] = SYN_MBUS_START_LONG;
buf[4] = c_field;
buf[5] = a_field;
buf[6] = ci_field;
if (payload_len > 0) {
memcpy(&buf[7], payload, payload_len);
}
/* Checksum is modulo-256 sum of C, A, CI, and Payload */
uint8_t chk = syn_mbus_calc_checksum(&buf[4], (size_t)l_field);
buf[7 + payload_len] = chk;
buf[8 + payload_len] = SYN_MBUS_STOP;
*out_len = total_len;
return SYN_OK;
}
/* ── One-Shot Decoder ────────────────────────────────────────────────────── */
SYN_Status syn_mbus_decode_frame(const uint8_t *buf, size_t len, SYN_MBUS_Frame *frame)
{
if (buf == NULL || frame == NULL || len == 0) {
return SYN_INVALID_PARAM;
}
memset(frame, 0, sizeof(*frame));
/* Case 1: Single character ACK (0xE5) */
if (len == 1 && buf[0] == SYN_MBUS_ACK_BYTE) {
frame->type = SYN_MBUS_FRAME_TYPE_SINGLE_ACK;
frame->checksum_valid = true;
return SYN_OK;
}
/* Case 2: Short frame (5 bytes: 0x10 | C | A | Checksum | 0x16) */
if (buf[0] == SYN_MBUS_START_SHORT) {
if (len < 5) {
return SYN_INVALID_PARAM;
}
if (buf[4] != SYN_MBUS_STOP) {
return SYN_ERROR;
}
frame->type = SYN_MBUS_FRAME_TYPE_SHORT;
frame->c_field = buf[1];
frame->a_field = buf[2];
frame->checksum = buf[3];
uint8_t calc_chk = (uint8_t)(buf[1] + buf[2]);
frame->checksum_valid = (calc_chk == buf[3]);
if (!frame->checksum_valid) {
return SYN_ERROR;
}
return SYN_OK;
}
/* Case 3: Control / Long frame (0x68 | L | L | 0x68 | C | A | CI | Payload... | Checksum |
* 0x16) */
if (buf[0] == SYN_MBUS_START_LONG) {
if (len < 9) {
return SYN_INVALID_PARAM;
}
uint8_t l1 = buf[1];
uint8_t l2 = buf[2];
if (l1 != l2) {
return SYN_ERROR;
}
if (buf[3] != SYN_MBUS_START_LONG) {
return SYN_ERROR;
}
size_t expected_total = (size_t)l1 + 6u;
if (len < expected_total) {
return SYN_INVALID_PARAM;
}
if (buf[expected_total - 1] != SYN_MBUS_STOP) {
return SYN_ERROR;
}
frame->c_field = buf[4];
frame->a_field = buf[5];
frame->ci_field = buf[6];
frame->checksum = buf[expected_total - 2];
if (l1 < 3) {
return SYN_ERROR;
}
frame->payload_len = (uint8_t)(l1 - 3u);
if (frame->payload_len == 0) {
frame->type = SYN_MBUS_FRAME_TYPE_CONTROL;
} else {
frame->type = SYN_MBUS_FRAME_TYPE_LONG;
memcpy(frame->payload, &buf[7], frame->payload_len);
}
uint8_t calc_chk = syn_mbus_calc_checksum(&buf[4], (size_t)l1);
frame->checksum_valid = (calc_chk == frame->checksum);
if (!frame->checksum_valid) {
return SYN_ERROR;
}
return SYN_OK;
}
return SYN_ERROR;
}
/* ── Streaming State Machine Decoder ─────────────────────────────────────── */
void syn_mbus_decoder_init(SYN_MBUS_Decoder *dec, SYN_MBUS_FrameCallback callback, void *ctx)
{
if (dec == NULL) {
return;
}
memset(dec, 0, sizeof(*dec));
dec->callback = callback;
dec->ctx = ctx;
dec->state = STATE_IDLE;
}
void syn_mbus_decoder_reset(SYN_MBUS_Decoder *dec)
{
if (dec == NULL) {
return;
}
dec->rx_idx = 0;
dec->expected_len = 0;
dec->state = STATE_IDLE;
}
void syn_mbus_decoder_feed(SYN_MBUS_Decoder *dec, uint8_t byte)
{
if (dec == NULL) {
return;
}
switch (dec->state) {
case STATE_IDLE:
dec->rx_idx = 0;
dec->rx_buf[dec->rx_idx++] = byte;
if (byte == SYN_MBUS_ACK_BYTE) {
/* Single character ACK complete! */
SYN_MBUS_Frame frame;
if (syn_mbus_decode_frame(dec->rx_buf, dec->rx_idx, &frame) == SYN_OK) {
if (dec->callback != NULL) {
dec->callback(&frame, dec->ctx);
}
}
syn_mbus_decoder_reset(dec);
} else if (byte == SYN_MBUS_START_SHORT) {
dec->state = STATE_SHORT_C;
} else if (byte == SYN_MBUS_START_LONG) {
dec->state = STATE_LONG_L1;
}
break;
/* ── Short Frame Parsing ────────────────────────────────────────────── */
case STATE_SHORT_C:
dec->rx_buf[dec->rx_idx++] = byte;
dec->state = STATE_SHORT_A;
break;
case STATE_SHORT_A:
dec->rx_buf[dec->rx_idx++] = byte;
dec->state = STATE_SHORT_CHK;
break;
case STATE_SHORT_CHK:
dec->rx_buf[dec->rx_idx++] = byte;
dec->state = STATE_SHORT_STOP;
break;
case STATE_SHORT_STOP:
dec->rx_buf[dec->rx_idx++] = byte;
if (byte == SYN_MBUS_STOP) {
SYN_MBUS_Frame frame;
if (syn_mbus_decode_frame(dec->rx_buf, dec->rx_idx, &frame) == SYN_OK) {
if (dec->callback != NULL) {
dec->callback(&frame, dec->ctx);
}
}
}
syn_mbus_decoder_reset(dec);
break;
/* ── Control / Long Frame Parsing ───────────────────────────────────── */
case STATE_LONG_L1:
dec->rx_buf[dec->rx_idx++] = byte;
dec->state = STATE_LONG_L2;
break;
case STATE_LONG_L2:
dec->rx_buf[dec->rx_idx++] = byte;
if (byte != dec->rx_buf[1]) {
/* Length byte mismatch -> reset */
syn_mbus_decoder_reset(dec);
break;
}
dec->expected_len = (size_t)byte + 6u;
dec->state = STATE_LONG_SOF2;
break;
case STATE_LONG_SOF2:
dec->rx_buf[dec->rx_idx++] = byte;
if (byte != SYN_MBUS_START_LONG) {
syn_mbus_decoder_reset(dec);
break;
}
dec->state = STATE_LONG_BODY;
break;
case STATE_LONG_BODY:
dec->rx_buf[dec->rx_idx++] = byte;
if (dec->rx_idx >= dec->expected_len) {
if (byte == SYN_MBUS_STOP) {
SYN_MBUS_Frame frame;
if (syn_mbus_decode_frame(dec->rx_buf, dec->rx_idx, &frame) == SYN_OK) {
if (dec->callback != NULL) {
dec->callback(&frame, dec->ctx);
}
}
}
syn_mbus_decoder_reset(dec);
}
break;
default:
syn_mbus_decoder_reset(dec);
break;
}
}
#endif /* SYN_USE_MBUS */