ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sunspec.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 sunspec.cpp
6 * @brief SunSpec Modbus model-chain walker + point readers + map writer (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_SUNSPEC
12
13#include <string.h>
14
15static uint16_t be16(const uint8_t *p)
16{
17 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
18}
19
20static uint32_t be32(const uint8_t *p)
21{
22 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
23}
24
25bool pc_sunspec_check_marker(const uint8_t *regs, size_t len)
26{
27 return regs && len >= 4 && be32(regs) == SUNSPEC_MARKER;
28}
29
30bool pc_sunspec_begin(const uint8_t *regs, size_t len, size_t *offset)
31{
32 if (!offset || !pc_sunspec_check_marker(regs, len))
33 {
34 return false;
35 }
36 *offset = 4; // past the 2-register marker
37 return true;
38}
39
40bool pc_sunspec_next_model(const uint8_t *regs, size_t len, size_t *offset, SunSpecModel *out)
41{
42 if (!regs || !offset || !out)
43 {
44 return false;
45 }
46 size_t o = *offset;
47 if (o + 4 > len) // need the [id][length] header
48 {
49 return false;
50 }
51 uint16_t id = be16(regs + o);
52 uint16_t length = be16(regs + o + 2);
53 if (id == SUNSPEC_END_MODEL)
54 {
55 return false; // map terminator
56 }
57 size_t body_len = (size_t)length * 2;
58 if (o + 4 + body_len > len) // body must be fully buffered
59 {
60 return false;
61 }
62 out->id = id;
63 out->length = length;
64 out->body = regs + o + 4;
65 out->body_len = body_len;
66 *offset = o + 4 + body_len;
67 return true;
68}
69
70uint16_t pc_sunspec_u16(const uint8_t *body, size_t reg)
71{
72 return be16(body + reg * 2);
73}
74
75int16_t pc_sunspec_i16(const uint8_t *body, size_t reg)
76{
77 return (int16_t)be16(body + reg * 2);
78}
79
80uint32_t pc_sunspec_u32(const uint8_t *body, size_t reg)
81{
82 return be32(body + reg * 2);
83}
84
85int32_t pc_sunspec_i32(const uint8_t *body, size_t reg)
86{
87 return (int32_t)be32(body + reg * 2);
88}
89
90bool pc_sunspec_string(const uint8_t *body, size_t reg, size_t nregs, char *out, size_t out_cap)
91{
92 if (!body || !out || out_cap == 0)
93 {
94 return false;
95 }
96 size_t avail = nregs * 2;
97 size_t i = 0;
98 const uint8_t *p = body + reg * 2;
99 for (; i < avail && i < out_cap - 1; i++)
100 {
101 if (p[i] == 0) // NUL padding ends the content
102 {
103 break;
104 }
105 out[i] = (char)p[i];
106 }
107 out[i] = '\0';
108 return true;
109}
110
111// ---- writer ----
112
113void pc_sunspec_writer_init(SunSpecWriter *w, uint8_t *buf, size_t cap)
114{
115 w->buf = buf;
116 w->cap = cap;
117 w->pos = 0;
118 w->error = false;
119}
120
121static bool ss_put(SunSpecWriter *w, const uint8_t *p, size_t n)
122{
123 if (w->error)
124 {
125 return false;
126 }
127 if (w->pos + n > w->cap)
128 {
129 w->error = true;
130 return false;
131 }
132 memcpy(w->buf + w->pos, p, n);
133 w->pos += n;
134 return true;
135}
136
137bool pc_sunspec_write_u16(SunSpecWriter *w, uint16_t v)
138{
139 uint8_t b[2] = {(uint8_t)(v >> 8), (uint8_t)v};
140 return ss_put(w, b, 2);
141}
142
143bool pc_sunspec_write_i16(SunSpecWriter *w, int16_t v)
144{
145 return pc_sunspec_write_u16(w, (uint16_t)v);
146}
147
148bool pc_sunspec_write_u32(SunSpecWriter *w, uint32_t v)
149{
150 uint8_t b[4] = {(uint8_t)(v >> 24), (uint8_t)(v >> 16), (uint8_t)(v >> 8), (uint8_t)v};
151 return ss_put(w, b, 4);
152}
153
154bool pc_sunspec_write_i32(SunSpecWriter *w, int32_t v)
155{
156 return pc_sunspec_write_u32(w, (uint32_t)v);
157}
158
159bool pc_sunspec_write_marker(SunSpecWriter *w)
160{
161 return pc_sunspec_write_u32(w, SUNSPEC_MARKER);
162}
163
164bool pc_sunspec_write_model_header(SunSpecWriter *w, uint16_t id, uint16_t length)
165{
166 return pc_sunspec_write_u16(w, id) && pc_sunspec_write_u16(w, length);
167}
168
169bool pc_sunspec_write_string(SunSpecWriter *w, const char *s, size_t nregs)
170{
171 if (!s)
172 {
173 return false;
174 }
175 size_t field = nregs * 2;
176 if (w->error)
177 {
178 return false;
179 }
180 if (w->pos + field > w->cap)
181 {
182 w->error = true;
183 return false;
184 }
185 size_t slen = strnlen(s, field);
186 for (size_t i = 0; i < field; i++)
187 {
188 w->buf[w->pos + i] = (i < slen) ? (uint8_t)s[i] : 0; // NUL-pad the remainder
189 }
190 w->pos += field;
191 return true;
192}
193
194bool pc_sunspec_write_end_model(SunSpecWriter *w)
195{
196 return pc_sunspec_write_u16(w, SUNSPEC_END_MODEL) && pc_sunspec_write_u16(w, 0);
197}
198
199size_t pc_sunspec_writer_finish(SunSpecWriter *w)
200{
201 return w->error ? 0 : w->pos;
202}
203
204#endif // PC_ENABLE_SUNSPEC
SunSpec Modbus device-information-model codec (PC_ENABLE_SUNSPEC) - zero-heap model-chain walker + re...