File syn_cjt188.c¶
File List > proto > syn_cjt188.c
Go to the documentation of this file
#include "syn_cjt188.h"
#include "../util/syn_pack.h"
#include <string.h>
uint8_t syn_cjt188_checksum(const uint8_t *buf, size_t len)
{
if (!buf || len == 0) {
return 0;
}
uint32_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum += buf[i];
}
return (uint8_t)(sum & 0xFFU);
}
size_t syn_cjt188_encode_read_req(uint8_t *out_buf, size_t buf_size, uint8_t meter_type,
const uint8_t meter_id[5], const uint8_t vendor_id[2],
uint16_t data_id, uint8_t seq)
{
/*
* Frame layout with 4-byte preamble:
* [0..3] FE FE FE FE
* [4] 68 (Start)
* [5] Meter Type (T)
* [6..10] Meter ID (5 bytes)
* [11..12] Vendor ID (2 bytes)
* [13] Control Code C (0x01)
* [14] Length L (0x03)
* [15..16] Data ID DI (2 bytes: e.g. 0x1F, 0x90)
* [17] SER Sequence Number
* [18] CS Checksum (sum from byte 4 to 17)
* [19] 16 (End)
* Total length: 20 bytes
*/
if (!out_buf || !meter_id || !vendor_id || buf_size < 20) {
return 0;
}
/* Preamble */
out_buf[0] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[1] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[2] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[3] = SYN_CJT188_PREAMBLE_BYTE;
/* Start of CJ/T 188 frame */
size_t start_idx = 4;
out_buf[start_idx] = SYN_CJT188_START_BYTE;
out_buf[start_idx + 1] = meter_type;
memcpy(&out_buf[start_idx + 2], meter_id, 5);
memcpy(&out_buf[start_idx + 7], vendor_id, 2);
out_buf[start_idx + 9] = SYN_CJT188_CTRL_READ_DATA;
out_buf[start_idx + 10] = 0x03; /* Fixed length: Data ID (2) + SER (1) */
/* Data ID in little-endian byte order as per CJ/T 188 standard */
out_buf[start_idx + 11] = (uint8_t)(data_id & 0xFFU);
out_buf[start_idx + 12] = (uint8_t)((data_id >> 8) & 0xFFU);
out_buf[start_idx + 13] = seq;
/* Compute CS over [start_idx .. start_idx + 13] (14 bytes total) */
uint8_t cs = syn_cjt188_checksum(&out_buf[start_idx], 14);
out_buf[start_idx + 14] = cs;
out_buf[start_idx + 15] = SYN_CJT188_END_BYTE;
return 20;
}
size_t syn_cjt188_encode_valve_ctrl(uint8_t *out_buf, size_t buf_size, uint8_t meter_type,
const uint8_t meter_id[5], const uint8_t vendor_id[2],
bool open_valve, uint8_t seq)
{
/*
* Frame layout with 4-byte preamble:
* [0..3] FE FE FE FE
* [4] 68 (Start)
* [5] Meter Type (T)
* [6..10] Meter ID (5 bytes)
* [11..12] Vendor ID (2 bytes)
* [13] Control Code C (0x04)
* [14] Length L (0x04: DI(2) + SER(1) + Cmd(1))
* [15..16] Data ID (0x17, 0xA0)
* [17] SER Sequence Number
* [18] Command byte (0x55 Open / 0x99 Close)
* [19] CS Checksum
* [20] 16 (End)
* Total length: 21 bytes
*/
if (!out_buf || !meter_id || !vendor_id || buf_size < 21) {
return 0;
}
out_buf[0] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[1] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[2] = SYN_CJT188_PREAMBLE_BYTE;
out_buf[3] = SYN_CJT188_PREAMBLE_BYTE;
size_t start_idx = 4;
out_buf[start_idx] = SYN_CJT188_START_BYTE;
out_buf[start_idx + 1] = meter_type;
memcpy(&out_buf[start_idx + 2], meter_id, 5);
memcpy(&out_buf[start_idx + 7], vendor_id, 2);
out_buf[start_idx + 9] = SYN_CJT188_CTRL_WRITE_DATA;
out_buf[start_idx + 10] = 0x04; /* DI (2) + SER (1) + Cmd (1) */
out_buf[start_idx + 11] = (uint8_t)(SYN_CJT188_DI_VALVE_CONTROL & 0xFFU);
out_buf[start_idx + 12] = (uint8_t)((SYN_CJT188_DI_VALVE_CONTROL >> 8) & 0xFFU);
out_buf[start_idx + 13] = seq;
out_buf[start_idx + 14] = open_valve ? SYN_CJT188_VALVE_OPEN : SYN_CJT188_VALVE_CLOSE;
uint8_t cs = syn_cjt188_checksum(&out_buf[start_idx], 15);
out_buf[start_idx + 15] = cs;
out_buf[start_idx + 16] = SYN_CJT188_END_BYTE;
return 21;
}
bool syn_cjt188_parse_frame(const uint8_t *buf, size_t len, SYN_CJT188_Frame *out_frame)
{
if (!buf || !out_frame || len < SYN_CJT188_MIN_FRAME_SIZE) {
return false;
}
/* Skip leading 0xFE preamble bytes if present */
size_t offset = 0;
while (offset < len && buf[offset] == SYN_CJT188_PREAMBLE_BYTE) {
offset++;
}
if ((len - offset) < SYN_CJT188_MIN_FRAME_SIZE) {
return false;
}
/* Verify Start Byte 0x68 */
if (buf[offset] != SYN_CJT188_START_BYTE) {
return false;
}
size_t data_len = buf[offset + 10];
size_t expected_total = 11 + data_len + 2; /* 11 header bytes + L + CS + 16 */
if ((len - offset) < expected_total) {
return false;
}
/* Verify End Byte 0x16 */
if (buf[offset + expected_total - 1] != SYN_CJT188_END_BYTE) {
return false;
}
/* Verify Checksum */
uint8_t expected_cs = buf[offset + expected_total - 2];
uint8_t actual_cs = syn_cjt188_checksum(&buf[offset], 11 + data_len);
if (expected_cs != actual_cs) {
return false;
}
/* Populate frame structure */
out_frame->meter_type = buf[offset + 1];
memcpy(out_frame->meter_id, &buf[offset + 2], 5);
memcpy(out_frame->vendor_id, &buf[offset + 7], 2);
out_frame->ctrl = buf[offset + 9];
out_frame->len = (uint8_t)data_len;
if (data_len >= 3) {
out_frame->data_id = (uint16_t)buf[offset + 11] | ((uint16_t)buf[offset + 12] << 8);
out_frame->seq = buf[offset + 13];
out_frame->payload = &buf[offset + 14];
out_frame->payload_len = data_len - 3;
} else {
out_frame->data_id = 0;
out_frame->seq = 0;
out_frame->payload = NULL;
out_frame->payload_len = 0;
}
return true;
}
void syn_cjt188_decoder_init(SYN_CJT188_Decoder *decoder)
{
if (decoder) {
memset(decoder, 0, sizeof(SYN_CJT188_Decoder));
}
}
bool syn_cjt188_decoder_feed(SYN_CJT188_Decoder *decoder, uint8_t byte, SYN_CJT188_Frame *out_frame)
{
if (!decoder || !out_frame) {
return false;
}
/* Handle preamble bytes */
if (!decoder->in_frame) {
if (byte == SYN_CJT188_PREAMBLE_BYTE) {
return false; /* Ignore preamble */
}
if (byte == SYN_CJT188_START_BYTE) {
decoder->in_frame = true;
decoder->index = 0;
decoder->buf[decoder->index++] = byte;
decoder->expected_len = 0;
return false;
}
return false;
}
/* Collect frame bytes */
if (decoder->index < SYN_CJT188_MAX_FRAME_SIZE) {
decoder->buf[decoder->index++] = byte;
} else {
/* Buffer overflow, reset decoder */
syn_cjt188_decoder_init(decoder);
return false;
}
/* Read length byte once offset 10 is reached */
if (decoder->index == 11) {
uint8_t data_len = decoder->buf[10];
decoder->expected_len = 11 + data_len + 2; /* 11 header + L + CS + 0x16 */
if (decoder->expected_len > SYN_CJT188_MAX_FRAME_SIZE) {
syn_cjt188_decoder_init(decoder);
return false;
}
}
/* When expected total length reached, attempt parsing */
if (decoder->expected_len > 0 && decoder->index >= decoder->expected_len) {
bool ok = syn_cjt188_parse_frame(decoder->buf, decoder->index, out_frame);
syn_cjt188_decoder_init(decoder);
return ok;
}
return false;
}