ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dmx.h
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.h
6 * @brief DMX512 framing + RDM (ANSI E1.20) management codec (PC_ENABLE_DMX).
7 *
8 * DMX512 (lighting / stage control over RS-485) is positional: after a break, a start code
9 * octet (0x00 for dimmer data) is followed by up to 512 channel slots, with no checksum or
10 * in-frame addressing. This codec assembles / reads that slot array, and implements **RDM**
11 * (Remote Device Management, ANSI E1.20) - the addressed management layer that shares the
12 * DMX wire: a real packet with 48-bit source / destination UIDs, a command class + parameter
13 * id, and a 16-bit additive checksum.
14 *
15 * The break + RS-485 direction are the application's (a `MAX485`-class transceiver on a UART
16 * at 250 kbit/s, 8N2). This is the byte-level framing layer. Pure and host-tested. Bridge a
17 * lighting rig onto Wi-Fi: drive DMX slots or discover / configure RDM fixtures from the web.
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef PROTOCORE_DMX_H
24#define PROTOCORE_DMX_H
25
26#include "protocore_config.h"
27
28#if PC_ENABLE_DMX
29
30#include <stddef.h>
31#include <stdint.h>
32
33#define DMX_MAX_CHANNELS 512u ///< slots per DMX512 universe
34#define DMX_SC_DIMMER 0x00u ///< start code for standard dimmer data
35
36#define RDM_SC 0xCCu ///< RDM start code (SC_RDM)
37#define RDM_SUB_SC 0x01u ///< RDM sub-start code (SC_SUB_MESSAGE)
38#define RDM_OVERHEAD 26u ///< full packet octets with PDL 0 (24-octet message + 2 checksum)
39
40// RDM command classes.
41#define RDM_CC_DISCOVERY 0x10u
42#define RDM_CC_DISCOVERY_RESPONSE 0x11u
43#define RDM_CC_GET 0x20u
44#define RDM_CC_GET_RESPONSE 0x21u
45#define RDM_CC_SET 0x30u
46#define RDM_CC_SET_RESPONSE 0x31u
47
48// RDM response types (carried in the port-id / response-type octet of a response).
49#define RDM_RESPONSE_ACK 0x00u
50#define RDM_RESPONSE_ACK_TIMER 0x01u
51#define RDM_RESPONSE_NACK_REASON 0x02u
52#define RDM_RESPONSE_ACK_OVERFLOW 0x03u
53
54// A few common RDM parameter ids (PIDs).
55#define RDM_PID_DISC_UNIQUE_BRANCH 0x0001u
56#define RDM_PID_DISC_MUTE 0x0002u
57#define RDM_PID_DISC_UN_MUTE 0x0003u
58#define RDM_PID_SUPPORTED_PARAMETERS 0x0050u
59#define RDM_PID_DEVICE_INFO 0x0060u
60#define RDM_PID_DMX_START_ADDRESS 0x00F0u
61#define RDM_PID_IDENTIFY_DEVICE 0x1000u
62
63// --- DMX512 ---
64
65/**
66 * @brief Assemble a DMX512 packet body: [start code][channel slots]. @p n <= 512.
67 * Returns the byte count (1 + n) or 0 on overflow. The break is the transport's job.
68 */
69size_t pc_dmx_build(uint8_t *buf, size_t cap, uint8_t start_code, const uint8_t *channels, uint16_t n);
70
71/**
72 * @brief Read channel @p ch (1-based, per DMX convention) from a received packet body.
73 * Returns the slot value, or 0 if @p ch is out of range / not present.
74 */
75uint8_t pc_dmx_get_channel(const uint8_t *buf, size_t len, uint16_t ch);
76
77// --- RDM (ANSI E1.20) ---
78
79/** @brief A parsed / to-be-built RDM packet. UIDs are 48-bit (manufacturer<<32 | device). */
80struct RdmPacket
81{
82 uint64_t dest_uid;
83 uint64_t src_uid;
84 uint8_t tn; ///< transaction number
85 uint8_t port_id; ///< port id (request) / response type (response)
86 uint8_t msg_count; ///< queued message count
87 uint16_t sub_device; ///< sub-device (0 = root)
88 uint8_t cc; ///< command class (RDM_CC_*)
89 uint16_t pid; ///< parameter id
90 uint8_t pdl; ///< parameter data length
91 const uint8_t *pdata; ///< parameter data (points into the parsed buffer); nullptr when pdl 0
92};
93
94/** @brief Compose a 48-bit RDM UID from a manufacturer id and a device id. */
95uint64_t pc_rdm_uid(uint16_t manufacturer, uint32_t device);
96
97/** @brief 16-bit additive checksum over @p len octets (RDM message block). */
98uint16_t pc_rdm_checksum(const uint8_t *buf, size_t len);
99
100/**
101 * @brief Build a full RDM packet (incl. the trailing 16-bit checksum) from @p p and its
102 * parameter data. Returns the total length (26 + pdl) or 0 on overflow.
103 */
104size_t pc_rdm_build(uint8_t *buf, size_t cap, const RdmPacket *p, const uint8_t *pdata, uint8_t pdl);
105
106/**
107 * @brief Parse an RDM packet: validates the start codes, the message length vs PDL, and the
108 * checksum. Fills @p out and @p consumed (the whole packet length).
109 */
110bool pc_rdm_parse(const uint8_t *buf, size_t len, RdmPacket *out, size_t *consumed);
111
112/**
113 * @brief Decode a DISC_UNIQUE_BRANCH discovery response into the responder's 48-bit UID. This reply is not a
114 * normal RDM packet: it is an optional 0xFE preamble (0..7 octets) + a 0xAA separator, then the 6 UID
115 * octets each sent as two copies OR'd with 0xAA / 0x55, then a 2-octet checksum sent the same way.
116 * Each original octet is recovered as the AND of its two encoded copies, and the checksum (the 16-bit
117 * additive sum of the 12 encoded UID octets) is verified.
118 * @return true iff the separator is present, the 16 encoded octets fit, and the checksum matches.
119 */
120bool pc_rdm_decode_disc_response(const uint8_t *buf, size_t len, uint64_t *uid);
121
122/**
123 * @brief Build the DISC_UNIQUE_BRANCH discovery response a responder sends for its 48-bit @p uid (the
124 * complement of pc_rdm_decode_disc_response): @p preamble_len octets of 0xFE (0..7) + the 0xAA
125 * separator + the 6 UID octets each as two copies OR'd with 0xAA / 0x55 + the 2-octet checksum (the
126 * 16-bit additive sum of the 12 encoded UID octets) sent the same way.
127 * @return octets written (@p preamble_len + 17), or 0 on a null buffer, @p preamble_len > 7, or overflow.
128 */
129size_t pc_rdm_build_disc_response(uint8_t *buf, size_t cap, uint64_t uid, uint8_t preamble_len);
130
131#define PC_RDM_DEVICE_INFO_PDL 19 ///< octets in a DEVICE_INFO (PID 0x0060) GET-response parameter block
132
133/** @brief Decoded DEVICE_INFO (PID 0x0060) parameter data - the descriptor every RDM responder must
134 * answer, carrying the fields a controller needs to patch and identify the device. */
135struct RdmDeviceInfo
136{
137 uint8_t proto_major; ///< RDM protocol version major (1 for E1.20)
138 uint8_t proto_minor; ///< RDM protocol version minor
139 uint16_t device_model_id; ///< manufacturer-specific device model id
140 uint16_t product_category; ///< E1.20 product category code
141 uint32_t software_version_id; ///< manufacturer-specific software version id
142 uint16_t dmx_footprint; ///< number of DMX512 slots the current personality occupies
143 uint8_t current_personality; ///< current DMX personality (1-based)
144 uint8_t personality_count; ///< total number of DMX personalities
145 uint16_t dmx_start_address; ///< DMX512 start address (1-512; 0xFFFF if the device uses no DMX)
146 uint16_t sub_device_count; ///< number of sub-devices (0 = none)
147 uint8_t sensor_count; ///< number of sensors
148};
149
150/**
151 * @brief Pack a DEVICE_INFO (PID 0x0060) GET-response parameter block from @p info into @p pdata: the
152 * 19-octet big-endian descriptor (protocol version, device model, product category, software
153 * version, DMX footprint / personality / start address, sub-device and sensor counts). Hand the
154 * result to pc_rdm_build as the pdata of a GET-response with pid RDM_PID_DEVICE_INFO.
155 * @return PC_RDM_DEVICE_INFO_PDL (19), or 0 on a null argument or @p cap < 19.
156 */
157size_t pc_rdm_build_device_info(uint8_t *pdata, size_t cap, const RdmDeviceInfo *info);
158
159/**
160 * @brief Decode a DEVICE_INFO (PID 0x0060) GET-response parameter block into @p out (the complement of
161 * pc_rdm_build_device_info).
162 * @return true iff @p pdl is at least 19 octets; false on a null argument or a short block.
163 */
164bool pc_rdm_parse_device_info(const uint8_t *pdata, uint8_t pdl, RdmDeviceInfo *out);
165
166#endif // PC_ENABLE_DMX
167#endif // PROTOCORE_DMX_H
User-facing configuration for ProtoCore.