ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_ENABLE_C37118
12
13#include <string.h>
14
15#include "shared_primitives/crc.h" // PC_CRC16_IBM_3740
17
18uint16_t pc_c37118_crc(const uint8_t *data, size_t len)
19{
20 // IEEE C37.118 uses CRC-CCITT (poly 0x1021, init 0xFFFF, unreflected), cataloged as
21 // CRC-16/IBM-3740. test_crc diffs the shared engine against the loop that used to live here
22 // over every length 0..64, so this is byte-identical to it.
23 return (uint16_t)pc_crc(&PC_CRC16_IBM_3740, data, len);
24}
25
26size_t pc_c37118_build_frame(uint8_t *buf, size_t cap, uint8_t type, uint8_t version, uint16_t idcode, uint32_t soc,
27 uint32_t fracsec, const uint8_t *payload, size_t payload_len)
28{
29 if (!buf || (payload_len && !payload))
30 {
31 return 0;
32 }
33 size_t total = C37118_MIN_FRAME + payload_len; // 14 header + payload + 2 CHK
34 if (total > 0xFFFF || total > cap)
35 {
36 return 0;
37 }
38 size_t p = 0;
39 buf[p++] = C37118_SYNC_LEADER;
40 buf[p++] = (uint8_t)(((type & C37118_TYPE_MASK) << C37118_TYPE_SHIFT) | (version & C37118_VERSION_MASK));
41 p += pc_wr16be(buf + p, (uint16_t)total);
42 p += pc_wr16be(buf + p, idcode);
43 p += pc_wr32be(buf + p, soc);
44 p += pc_wr32be(buf + p, fracsec);
45 if (payload_len)
46 {
47 memcpy(buf + p, payload, payload_len);
48 p += payload_len;
49 }
50 uint16_t crc = pc_c37118_crc(buf, p); // over everything before CHK
51 p += pc_wr16be(buf + p, crc);
52 return p;
53}
54
55size_t pc_c37118_build_command(uint8_t *buf, size_t cap, uint16_t idcode, uint32_t soc, uint32_t fracsec, uint16_t cmd)
56{
57 uint8_t payload[2];
58 pc_wr16be(payload, cmd);
59 return pc_c37118_build_frame(buf, cap, C37118_TYPE_CMD, C37118_VERSION_2011, idcode, soc, fracsec, payload, 2);
60}
61
62bool pc_c37118_parse_frame(const uint8_t *buf, size_t len, C37118Frame *out)
63{
64 if (!buf || !out || len < C37118_MIN_FRAME)
65 {
66 return false;
67 }
68 if (buf[0] != C37118_SYNC_LEADER)
69 {
70 return false;
71 }
72 uint16_t framesize = pc_rd16be(buf + 2);
73 if (framesize < C37118_MIN_FRAME || framesize > len)
74 {
75 return false; // out of range / not fully buffered
76 }
77 uint16_t want = pc_c37118_crc(buf, (size_t)framesize - 2);
78 uint16_t got = pc_rd16be(buf + framesize - 2);
79 if (want != got)
80 {
81 return false; // CHK mismatch
82 }
83 out->type = (uint8_t)((buf[1] >> C37118_TYPE_SHIFT) & C37118_TYPE_MASK);
84 out->version = (uint8_t)(buf[1] & C37118_VERSION_MASK);
85 out->framesize = framesize;
86 out->idcode = pc_rd16be(buf + 4);
87 out->soc = pc_rd32be(buf + 6);
88 out->fracsec = pc_rd32be(buf + 10);
89 out->data = buf + 14;
90 out->data_len = (size_t)framesize - C37118_MIN_FRAME;
91 return true;
92}
93
94bool pc_c37118_parse_command(const C37118Frame *f, uint16_t *cmd)
95{
96 if (!f || f->type != C37118_TYPE_CMD || f->data_len < 2)
97 {
98 return false;
99 }
100 if (cmd)
101 {
102 *cmd = pc_rd16be(f->data);
103 }
104 return true;
105}
106
107bool pc_c37118_decode_stat(const C37118Frame *f, C37118Stat *out)
108{
109 if (!f || !out || f->type != C37118_TYPE_DATA || f->data_len < 2)
110 {
111 return false;
112 }
113 uint16_t s = pc_rd16be(f->data); // STAT is the first word of the data payload, big-endian
114 out->raw = s;
115 out->data_valid = (s & 0x8000u) == 0; // bit 15: 0 = valid
116 out->pmu_error = (s & 0x4000u) != 0; // bit 14
117 out->in_sync = (s & 0x2000u) == 0; // bit 13: 0 = in sync
118 out->sorted_by_arrival = (s & 0x1000u) != 0; // bit 12
119 out->trigger = (s & 0x0800u) != 0; // bit 11
120 out->config_change = (s & 0x0400u) != 0; // bit 10
121 out->data_modified = (s & 0x0200u) != 0; // bit 9
122 out->time_quality = (uint8_t)((s >> 6) & 0x07u); // bits 8-6
123 out->unlocked_time = (uint8_t)((s >> 4) & 0x03u); // bits 5-4
124 out->trigger_reason = (uint8_t)(s & 0x0Fu); // bits 3-0
125 return true;
126}
127
128#endif // PC_ENABLE_C37118
IEEE C37.118.2 synchrophasor frame codec (PC_ENABLE_C37118) - zero-heap frame builder + parser for th...
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_IBM_3740
CRC-16/IBM-3740 (often called CCITT-FALSE). check = 0x29B1.
Definition crc.h:184
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint32_t pc_rd32be(const uint8_t *p)
Read a big-endian u32 at p.
Definition endian.h:119
size_t pc_wr16be(uint8_t *p, uint16_t v)
Write v big-endian at p.
Definition endian.h:85
uint16_t pc_rd16be(const uint8_t *p)
Read a big-endian u16 at p.
Definition endian.h:113
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93