ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dmx.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 dmx.cpp
6 * @brief DMX512 + RDM (ANSI E1.20) codec (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_DMX
12
13#include <string.h>
14
15size_t pc_dmx_build(uint8_t *buf, size_t cap, uint8_t start_code, const uint8_t *channels, uint16_t n)
16{
17 if (!buf || n > DMX_MAX_CHANNELS || (n && !channels))
18 {
19 return 0;
20 }
21 size_t total = (size_t)1 + n;
22 if (cap < total)
23 {
24 return 0;
25 }
26 buf[0] = start_code;
27 if (n)
28 {
29 memcpy(buf + 1, channels, n);
30 }
31 return total;
32}
33
34uint8_t pc_dmx_get_channel(const uint8_t *buf, size_t len, uint16_t ch)
35{
36 if (!buf || ch < 1 || ch > DMX_MAX_CHANNELS || (size_t)ch >= len)
37 {
38 return 0; // slot ch lives at buf[ch] (buf[0] is the start code)
39 }
40 return buf[ch];
41}
42
43uint64_t pc_rdm_uid(uint16_t manufacturer, uint32_t device)
44{
45 return ((uint64_t)manufacturer << 32) | device;
46}
47
48uint16_t pc_rdm_checksum(const uint8_t *buf, size_t len)
49{
50 uint16_t s = 0;
51 for (size_t i = 0; i < len; i++)
52 {
53 s = (uint16_t)(s + buf[i]);
54 }
55 return s;
56}
57
58// Write a 48-bit UID big-endian (manufacturer high).
59static void put_uid(uint8_t *p, uint64_t uid)
60{
61 p[0] = (uint8_t)(uid >> 40);
62 p[1] = (uint8_t)(uid >> 32);
63 p[2] = (uint8_t)(uid >> 24);
64 p[3] = (uint8_t)(uid >> 16);
65 p[4] = (uint8_t)(uid >> 8);
66 p[5] = (uint8_t)uid;
67}
68
69static uint64_t get_uid(const uint8_t *p)
70{
71 return ((uint64_t)p[0] << 40) | ((uint64_t)p[1] << 32) | ((uint64_t)p[2] << 24) | ((uint64_t)p[3] << 16) |
72 ((uint64_t)p[4] << 8) | (uint64_t)p[5];
73}
74
75size_t pc_rdm_build(uint8_t *buf, size_t cap, const RdmPacket *p, const uint8_t *pdata, uint8_t pdl)
76{
77 if (!buf || !p || (pdl && !pdata))
78 {
79 return 0;
80 }
81 uint8_t ml = (uint8_t)(24 + pdl); // message length: SC..end of parameter data (excludes checksum)
82 size_t total = (size_t)ml + 2;
83 if (cap < total)
84 {
85 return 0;
86 }
87 buf[0] = RDM_SC;
88 buf[1] = RDM_SUB_SC;
89 buf[2] = ml;
90 put_uid(buf + 3, p->dest_uid);
91 put_uid(buf + 9, p->src_uid);
92 buf[15] = p->tn;
93 buf[16] = p->port_id;
94 buf[17] = p->msg_count;
95 buf[18] = (uint8_t)(p->sub_device >> 8); // sub-device, big-endian
96 buf[19] = (uint8_t)p->sub_device;
97 buf[20] = p->cc;
98 buf[21] = (uint8_t)(p->pid >> 8); // PID, big-endian
99 buf[22] = (uint8_t)p->pid;
100 buf[23] = pdl;
101 if (pdl)
102 {
103 memcpy(buf + 24, pdata, pdl);
104 }
105 uint16_t cs = pc_rdm_checksum(buf, ml); // checksum over SC..end of parameter data
106 buf[ml] = (uint8_t)(cs >> 8);
107 buf[ml + 1] = (uint8_t)cs;
108 return total;
109}
110
111bool pc_rdm_parse(const uint8_t *buf, size_t len, RdmPacket *out, size_t *consumed)
112{
113 if (!buf || !out || len < RDM_OVERHEAD)
114 {
115 return false;
116 }
117 if (buf[0] != RDM_SC || buf[1] != RDM_SUB_SC)
118 {
119 return false;
120 }
121 uint8_t ml = buf[2];
122 if (ml < 24)
123 {
124 return false;
125 }
126 uint8_t pdl = buf[23];
127 if (ml != (uint8_t)(24 + pdl))
128 {
129 return false; // message length must match the declared PDL
130 }
131 size_t total = (size_t)ml + 2;
132 if (len < total)
133 {
134 return false;
135 }
136 uint16_t cs = (uint16_t)((buf[ml] << 8) | buf[ml + 1]);
137 if (cs != pc_rdm_checksum(buf, ml))
138 {
139 return false;
140 }
141
142 out->dest_uid = get_uid(buf + 3);
143 out->src_uid = get_uid(buf + 9);
144 out->tn = buf[15];
145 out->port_id = buf[16];
146 out->msg_count = buf[17];
147 out->sub_device = (uint16_t)((buf[18] << 8) | buf[19]);
148 out->cc = buf[20];
149 out->pid = (uint16_t)((buf[21] << 8) | buf[22]);
150 out->pdl = pdl;
151 out->pdata = pdl ? buf + 24 : nullptr;
152 if (consumed)
153 {
154 *consumed = total;
155 }
156 return true;
157}
158
159bool pc_rdm_decode_disc_response(const uint8_t *buf, size_t len, uint64_t *uid)
160{
161 if (!buf || !uid)
162 {
163 return false;
164 }
165 // Skip the optional preamble (up to 7 octets of 0xFE), then require the 0xAA separator.
166 size_t p = 0;
167 while (p < len && p < 7 && buf[p] == 0xFE)
168 {
169 p++;
170 }
171 if (p >= len || buf[p] != 0xAA)
172 {
173 return false;
174 }
175 p++;
176 if (len - p < 16) // 12 encoded UID octets + 4 encoded checksum octets
177 {
178 return false;
179 }
180 const uint8_t *euid = buf + p;
181 // The checksum is the 16-bit additive sum of the 12 encoded UID octets.
182 uint16_t sum = 0;
183 for (int i = 0; i < 12; i++)
184 {
185 sum = (uint16_t)(sum + euid[i]);
186 }
187 uint8_t csum_hi = (uint8_t)(euid[12] & euid[13]); // AND the two encoded copies to recover the octet
188 uint8_t csum_lo = (uint8_t)(euid[14] & euid[15]);
189 if ((uint16_t)(((uint16_t)csum_hi << 8) | csum_lo) != sum)
190 {
191 return false;
192 }
193 // Recover the 6 UID octets (MSB first); each is the AND of its 0xAA / 0x55 encoded copies.
194 uint64_t u = 0;
195 for (int i = 0; i < 6; i++)
196 {
197 u = (u << 8) | (uint8_t)(euid[i * 2] & euid[i * 2 + 1]);
198 }
199 *uid = u;
200 return true;
201}
202
203size_t pc_rdm_build_disc_response(uint8_t *buf, size_t cap, uint64_t uid, uint8_t preamble_len)
204{
205 if (!buf || preamble_len > 7) // E1.20 allows 0..7 preamble octets
206 {
207 return 0;
208 }
209 size_t total = (size_t)preamble_len + 1 + 16; // preamble + 0xAA separator + 12 UID + 4 checksum octets
210 if (cap < total)
211 {
212 return 0;
213 }
214 size_t p = 0;
215 for (uint8_t i = 0; i < preamble_len; i++)
216 {
217 buf[p++] = 0xFE;
218 }
219 buf[p++] = 0xAA;
220 // Encode the 6 UID octets (MSB first): each byte b -> (b | 0xAA), (b | 0x55); sum the encoded octets.
221 uint16_t sum = 0;
222 for (int i = 0; i < 6; i++)
223 {
224 uint8_t b = (uint8_t)(uid >> (8 * (5 - i)));
225 uint8_t e0 = (uint8_t)(b | 0xAA);
226 uint8_t e1 = (uint8_t)(b | 0x55);
227 buf[p++] = e0;
228 buf[p++] = e1;
229 sum = (uint16_t)(sum + e0 + e1);
230 }
231 uint8_t csum_hi = (uint8_t)(sum >> 8);
232 uint8_t csum_lo = (uint8_t)(sum & 0xFF);
233 buf[p++] = (uint8_t)(csum_hi | 0xAA);
234 buf[p++] = (uint8_t)(csum_hi | 0x55);
235 buf[p++] = (uint8_t)(csum_lo | 0xAA);
236 buf[p++] = (uint8_t)(csum_lo | 0x55);
237 return p;
238}
239
240size_t pc_rdm_build_device_info(uint8_t *pdata, size_t cap, const RdmDeviceInfo *info)
241{
242 if (!pdata || !info || cap < PC_RDM_DEVICE_INFO_PDL)
243 {
244 return 0;
245 }
246 pdata[0] = info->proto_major;
247 pdata[1] = info->proto_minor;
248 pdata[2] = (uint8_t)(info->device_model_id >> 8); // all multi-octet fields big-endian
249 pdata[3] = (uint8_t)info->device_model_id;
250 pdata[4] = (uint8_t)(info->product_category >> 8);
251 pdata[5] = (uint8_t)info->product_category;
252 pdata[6] = (uint8_t)(info->software_version_id >> 24);
253 pdata[7] = (uint8_t)(info->software_version_id >> 16);
254 pdata[8] = (uint8_t)(info->software_version_id >> 8);
255 pdata[9] = (uint8_t)info->software_version_id;
256 pdata[10] = (uint8_t)(info->dmx_footprint >> 8);
257 pdata[11] = (uint8_t)info->dmx_footprint;
258 pdata[12] = info->current_personality;
259 pdata[13] = info->personality_count;
260 pdata[14] = (uint8_t)(info->dmx_start_address >> 8);
261 pdata[15] = (uint8_t)info->dmx_start_address;
262 pdata[16] = (uint8_t)(info->sub_device_count >> 8);
263 pdata[17] = (uint8_t)info->sub_device_count;
264 pdata[18] = info->sensor_count;
265 return PC_RDM_DEVICE_INFO_PDL;
266}
267
268bool pc_rdm_parse_device_info(const uint8_t *pdata, uint8_t pdl, RdmDeviceInfo *out)
269{
270 if (!pdata || !out || pdl < PC_RDM_DEVICE_INFO_PDL)
271 {
272 return false;
273 }
274 out->proto_major = pdata[0];
275 out->proto_minor = pdata[1];
276 out->device_model_id = (uint16_t)((pdata[2] << 8) | pdata[3]);
277 out->product_category = (uint16_t)((pdata[4] << 8) | pdata[5]);
278 out->software_version_id =
279 ((uint32_t)pdata[6] << 24) | ((uint32_t)pdata[7] << 16) | ((uint32_t)pdata[8] << 8) | (uint32_t)pdata[9];
280 out->dmx_footprint = (uint16_t)((pdata[10] << 8) | pdata[11]);
281 out->current_personality = pdata[12];
282 out->personality_count = pdata[13];
283 out->dmx_start_address = (uint16_t)((pdata[14] << 8) | pdata[15]);
284 out->sub_device_count = (uint16_t)((pdata[16] << 8) | pdata[17]);
285 out->sensor_count = pdata[18];
286 return true;
287}
288
289#endif // PC_ENABLE_DMX
DMX512 framing + RDM (ANSI E1.20) management codec (PC_ENABLE_DMX).