DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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
9#include "services/dmx/dmx.h"
10
11#if DETWS_ENABLE_DMX
12
13#include <string.h>
14
15size_t 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 return 0;
19 size_t total = (size_t)1 + n;
20 if (cap < total)
21 return 0;
22 buf[0] = start_code;
23 if (n)
24 memcpy(buf + 1, channels, n);
25 return total;
26}
27
28uint8_t dmx_get_channel(const uint8_t *buf, size_t len, uint16_t ch)
29{
30 if (!buf || ch < 1 || ch > DMX_MAX_CHANNELS || (size_t)ch >= len)
31 return 0; // slot ch lives at buf[ch] (buf[0] is the start code)
32 return buf[ch];
33}
34
35uint64_t rdm_uid(uint16_t manufacturer, uint32_t device)
36{
37 return ((uint64_t)manufacturer << 32) | device;
38}
39
40uint16_t rdm_checksum(const uint8_t *buf, size_t len)
41{
42 uint16_t s = 0;
43 for (size_t i = 0; i < len; i++)
44 s = (uint16_t)(s + buf[i]);
45 return s;
46}
47
48// Write a 48-bit UID big-endian (manufacturer high).
49static void put_uid(uint8_t *p, uint64_t uid)
50{
51 p[0] = (uint8_t)(uid >> 40);
52 p[1] = (uint8_t)(uid >> 32);
53 p[2] = (uint8_t)(uid >> 24);
54 p[3] = (uint8_t)(uid >> 16);
55 p[4] = (uint8_t)(uid >> 8);
56 p[5] = (uint8_t)uid;
57}
58
59static uint64_t get_uid(const uint8_t *p)
60{
61 return ((uint64_t)p[0] << 40) | ((uint64_t)p[1] << 32) | ((uint64_t)p[2] << 24) | ((uint64_t)p[3] << 16) |
62 ((uint64_t)p[4] << 8) | (uint64_t)p[5];
63}
64
65size_t rdm_build(uint8_t *buf, size_t cap, const RdmPacket *p, const uint8_t *pdata, uint8_t pdl)
66{
67 if (!buf || !p || (pdl && !pdata))
68 return 0;
69 uint8_t ml = (uint8_t)(24 + pdl); // message length: SC..end of parameter data (excludes checksum)
70 size_t total = (size_t)ml + 2;
71 if (cap < total)
72 return 0;
73 buf[0] = RDM_SC;
74 buf[1] = RDM_SUB_SC;
75 buf[2] = ml;
76 put_uid(buf + 3, p->dest_uid);
77 put_uid(buf + 9, p->src_uid);
78 buf[15] = p->tn;
79 buf[16] = p->port_id;
80 buf[17] = p->msg_count;
81 buf[18] = (uint8_t)(p->sub_device >> 8); // sub-device, big-endian
82 buf[19] = (uint8_t)p->sub_device;
83 buf[20] = p->cc;
84 buf[21] = (uint8_t)(p->pid >> 8); // PID, big-endian
85 buf[22] = (uint8_t)p->pid;
86 buf[23] = pdl;
87 if (pdl)
88 memcpy(buf + 24, pdata, pdl);
89 uint16_t cs = rdm_checksum(buf, ml); // checksum over SC..end of parameter data
90 buf[ml] = (uint8_t)(cs >> 8);
91 buf[ml + 1] = (uint8_t)cs;
92 return total;
93}
94
95bool rdm_parse(const uint8_t *buf, size_t len, RdmPacket *out, size_t *consumed)
96{
97 if (!buf || !out || len < RDM_OVERHEAD)
98 return false;
99 if (buf[0] != RDM_SC || buf[1] != RDM_SUB_SC)
100 return false;
101 uint8_t ml = buf[2];
102 if (ml < 24)
103 return false;
104 uint8_t pdl = buf[23];
105 if (ml != (uint8_t)(24 + pdl))
106 return false; // message length must match the declared PDL
107 size_t total = (size_t)ml + 2;
108 if (len < total)
109 return false;
110 uint16_t cs = (uint16_t)((buf[ml] << 8) | buf[ml + 1]);
111 if (cs != rdm_checksum(buf, ml))
112 return false;
113
114 out->dest_uid = get_uid(buf + 3);
115 out->src_uid = get_uid(buf + 9);
116 out->tn = buf[15];
117 out->port_id = buf[16];
118 out->msg_count = buf[17];
119 out->sub_device = (uint16_t)((buf[18] << 8) | buf[19]);
120 out->cc = buf[20];
121 out->pid = (uint16_t)((buf[21] << 8) | buf[22]);
122 out->pdl = pdl;
123 out->pdata = pdl ? buf + 24 : nullptr;
124 if (consumed)
125 *consumed = total;
126 return true;
127}
128
129#endif // DETWS_ENABLE_DMX
DMX512 framing + RDM (ANSI E1.20) management codec (DETWS_ENABLE_DMX).