DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
c37118.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file c37118.cpp
6 * @brief IEEE C37.118.2 synchrophasor frame builder + parser (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_C37118
12
13#include <string.h>
14
15uint16_t c37118_crc(const uint8_t *data, size_t len)
16{
17 uint16_t crc = 0xFFFF;
18 for (size_t i = 0; i < len; i++)
19 {
20 crc ^= (uint16_t)data[i] << 8;
21 for (int b = 0; b < 8; b++)
22 crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1);
23 }
24 return crc;
25}
26
27static size_t put16(uint8_t *p, uint16_t v)
28{
29 p[0] = (uint8_t)(v >> 8);
30 p[1] = (uint8_t)v;
31 return 2;
32}
33
34static size_t put32(uint8_t *p, uint32_t v)
35{
36 p[0] = (uint8_t)(v >> 24);
37 p[1] = (uint8_t)(v >> 16);
38 p[2] = (uint8_t)(v >> 8);
39 p[3] = (uint8_t)v;
40 return 4;
41}
42
43static uint16_t get16(const uint8_t *p)
44{
45 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
46}
47
48static uint32_t get32(const uint8_t *p)
49{
50 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
51}
52
53size_t c37118_build_frame(uint8_t *buf, size_t cap, uint8_t type, uint8_t version, uint16_t idcode, uint32_t soc,
54 uint32_t fracsec, const uint8_t *payload, size_t payload_len)
55{
56 if (!buf || (payload_len && !payload))
57 return 0;
58 size_t total = C37118_MIN_FRAME + payload_len; // 14 header + payload + 2 CHK
59 if (total > 0xFFFF || total > cap)
60 return 0;
61 size_t p = 0;
62 buf[p++] = C37118_SYNC_LEADER;
63 buf[p++] = (uint8_t)(((type & C37118_TYPE_MASK) << C37118_TYPE_SHIFT) | (version & C37118_VERSION_MASK));
64 p += put16(buf + p, (uint16_t)total);
65 p += put16(buf + p, idcode);
66 p += put32(buf + p, soc);
67 p += put32(buf + p, fracsec);
68 if (payload_len)
69 {
70 memcpy(buf + p, payload, payload_len);
71 p += payload_len;
72 }
73 uint16_t crc = c37118_crc(buf, p); // over everything before CHK
74 p += put16(buf + p, crc);
75 return p;
76}
77
78size_t c37118_build_command(uint8_t *buf, size_t cap, uint16_t idcode, uint32_t soc, uint32_t fracsec, uint16_t cmd)
79{
80 uint8_t payload[2];
81 put16(payload, cmd);
82 return c37118_build_frame(buf, cap, C37118_TYPE_CMD, C37118_VERSION_2011, idcode, soc, fracsec, payload, 2);
83}
84
85bool c37118_parse_frame(const uint8_t *buf, size_t len, C37118Frame *out)
86{
87 if (!buf || !out || len < C37118_MIN_FRAME)
88 return false;
89 if (buf[0] != C37118_SYNC_LEADER)
90 return false;
91 uint16_t framesize = get16(buf + 2);
92 if (framesize < C37118_MIN_FRAME || framesize > len)
93 return false; // out of range / not fully buffered
94 uint16_t want = c37118_crc(buf, (size_t)framesize - 2);
95 uint16_t got = get16(buf + framesize - 2);
96 if (want != got)
97 return false; // CHK mismatch
98 out->type = (uint8_t)((buf[1] >> C37118_TYPE_SHIFT) & C37118_TYPE_MASK);
99 out->version = (uint8_t)(buf[1] & C37118_VERSION_MASK);
100 out->framesize = framesize;
101 out->idcode = get16(buf + 4);
102 out->soc = get32(buf + 6);
103 out->fracsec = get32(buf + 10);
104 out->data = buf + 14;
105 out->data_len = (size_t)framesize - C37118_MIN_FRAME;
106 return true;
107}
108
109bool c37118_parse_command(const C37118Frame *f, uint16_t *cmd)
110{
111 if (!f || f->type != C37118_TYPE_CMD || f->data_len < 2)
112 return false;
113 if (cmd)
114 *cmd = get16(f->data);
115 return true;
116}
117
118#endif // DETWS_ENABLE_C37118
IEEE C37.118.2 synchrophasor frame codec (DETWS_ENABLE_C37118) - zero-heap frame builder + parser for...