{"id":38952347,"url":"https://github.com/someweisguy/esp_dmx","last_synced_at":"2026-01-17T16:10:42.792Z","repository":{"id":37209795,"uuid":"323453236","full_name":"someweisguy/esp_dmx","owner":"someweisguy","description":"Espressif ESP32 implementation of ANSI-ESTA E1.11 DMX-512A and E1.20 RDM","archived":false,"fork":false,"pushed_at":"2024-06-14T02:41:40.000Z","size":2932,"stargazers_count":301,"open_issues_count":5,"forks_count":30,"subscribers_count":16,"default_branch":"release/v4.1","last_synced_at":"2024-06-14T03:46:52.363Z","etag":null,"topics":["dmx","dmx-512","dmx-512a","dmx-devices","dmx512","dmx512a","esp-dmx","esp32","lighting","rdm","stage","theater","theatre"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/someweisguy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"custom":["paypal.me/someweisguy"]}},"created_at":"2020-12-21T21:36:34.000Z","updated_at":"2024-06-14T02:41:44.000Z","dependencies_parsed_at":"2023-12-03T08:24:28.704Z","dependency_job_id":"d4686660-d86d-4551-9358-5fe010cb84a6","html_url":"https://github.com/someweisguy/esp_dmx","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/someweisguy/esp_dmx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/someweisguy%2Fesp_dmx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/someweisguy%2Fesp_dmx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/someweisguy%2Fesp_dmx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/someweisguy%2Fesp_dmx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/someweisguy","download_url":"https://codeload.github.com/someweisguy/esp_dmx/tar.gz/refs/heads/release/v4.1","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/someweisguy%2Fesp_dmx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28511852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dmx","dmx-512","dmx-512a","dmx-devices","dmx512","dmx512a","esp-dmx","esp32","lighting","rdm","stage","theater","theatre"],"created_at":"2026-01-17T16:10:42.690Z","updated_at":"2026-01-17T16:10:42.774Z","avatar_url":"https://github.com/someweisguy.png","language":"C","funding_links":["paypal.me/someweisguy"],"categories":[],"sub_categories":[],"readme":"# esp_dmx\n\nThis library allows for transmitting and receiving ANSI-ESTA E1.11 DMX-512A and ANSI-ESTA E1.20 RDM using an Espressif ESP32. It provides control and analysis of the packet configuration and allows the user to read or write synchronously or asynchronously from the DMX bus using whichever hardware UART port that is desired. This library also includes tools for data error-checking to safely process DMX and RDM commands as well as DMX packet metadata extraction to assist with troubleshooting errors.\n\n## Contents\n\n- [Library Installation](#library-installation)\n  - [Arduino](#arduino)\n  - [ESP-IDF](#esp-idf)\n  - [PlatformIO](#platformio)\n- [Quick-Start Guide](#quick-start-guide)\n- [What is DMX?](#what-is-dmx)\n  - [What is RDM?](#what-is-rdm)\n- [DMX Basics](#dmx-basics)\n  - [Addresses and the Start Code](#addresses-and-the-start-code)\n  - [Footprints](#footprints)\n  - [Universes](#universes)\n- [RDM Basics](#rdm-basics)\n  - [Unique IDs](#unique-ids)\n  - [Sub-devices](#sub-devices)\n  - [Parameters](#parameters)\n  - [Discovery](#discovery)\n  - [Responses](#responses)\n- [Configuring the DMX Port](#configuring-the-dmx-port)\n  - [Installing the Driver](#installing-the-driver)\n  - [Setting Communication Pins](#setting-communication-pins)\n  - [Timing Configuration](#timing-configuration)\n- [Reading and Writing DMX](#reading-and-writing-dmx)\n  - [Reading DMX](#reading-dmx)\n  - [DMX Sniffer](#dmx-sniffer)\n  - [Writing DMX](#writing-dmx)\n  - [DMX Parameters](#dmx-parameters)\n- [Reading and Writing RDM](#reading-and-writing-rdm)\n  - [RDM Requests](#rdm-requests)\n  - [Discovering Devices](#discovering-devices)\n  - [RDM Responder](#rdm-responder)\n- [Error Handling](#error-handling)\n  - [Timing Macros](#timing-macros)\n  - [DMX Start Codes](#dmx-start-codes)\n- [Additional Considerations](#additional-considerations)\n  - [Using Flash or Disabling Cache](#using-flash-or-disabling-cache)\n  - [Wiring an RS-485 Circuit](#wiring-an-rs-485-circuit)\n  - [Hardware Specifications](#hardware-specifications)\n- [To Do](#to-do)\n- [Appendix](#appendix)\n  - [Command Classes](#command-classes)\n  - [NACK Reason Codes](#nack-reason-codes)\n  - [Parameter IDs](#parameter-ids)\n  - [Product Categories](#product-categories)\n  - [Response Types](#response-types)\n\n## Library Installation\n\n### Arduino\n\nThis library requires the Arduino-ESP32 framework version 2.0.3 or newer. To install the correct framework, follow Espressif's instructions on the Arduino-ESP32 documentation page [here](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html).\n\nThis library can be installed by cloning this repository into your your `Arduino/libaries` folder or by searching for `esp_dmx` in the Arduino IDE Library Manager. Then simply include the library by adding `#include \"esp_dmx.h\"` at the top of your Arduino sketch.\n\n### ESP-IDF\n\nThis library requires ESP-IDF version 4.4.1 or newer. Clone this repository into your project's `components` folder. The library can be linked by writing `#include \"esp_dmx.h\"` at the top of your `main.c` file.\n\n### PlatformIO\n\nThis library is compatible with the PlatformIO IDE. Search for this library in the PlatformIO library registry and add it to your project. The library can be included by writing `#include \"esp_dmx.h\"` at the top of your `main.c` or `main.cpp` file.\n\nThis library includes a `Kconfig` file for configuring build options on the ESP32. When using the ESP-IDF framework, it is recommended to move library folders to a `components` folder located in your project's root directory rather than leaving PlatformIO installed libraries in their default location. This is not required but it can result in more a more performant driver. See [Using Flash or Disabling Cache](#using-flash-or-disabling-cache) for more information.\n\n## Quick-Start Guide\n\nTo get started, call the following code in your `setup()` function if using Arduino, or `app_main()` in your `main.c` file if using ESP-IDF.\n\n```c\nconst dmx_port_t dmx_num = DMX_NUM_1;\n\n// First, use the default DMX configuration...\ndmx_config_t config = DMX_CONFIG_DEFAULT;\n\n// ...declare the driver's DMX personalities...\nconst int personality_count = 1;\ndmx_personality_t personalities[] = {\n  {1, \"Default Personality\"}\n};\n\n// ...install the DMX driver...\ndmx_driver_install(dmx_num, \u0026config, personalities, personality_count);\n\n// ...and then set the communication pins!\nconst int tx_pin = 17;\nconst int rx_pin = 16;\nconst int rts_pin = 21;\ndmx_set_pin(dmx_num, tx_pin, rx_pin, rts_pin);\n```\n\nTo write data to the DMX bus, two functions are provided. The function `dmx_write()` writes data to the DMX buffer and `dmx_send()` sends the data out onto the bus. The function `dmx_wait_sent()` is used to block the task until the DMX bus is idle.\n\n```c\nuint8_t data[DMX_PACKET_SIZE] = {0};\n\nwhile (true) {\n  // Write to the packet and send it.\n  dmx_write(dmx_num, data, DMX_PACKET_SIZE);\n  dmx_send(dmx_num);\n  \n  // Do work here...\n\n  // Block until the packet is finished sending.\n  dmx_wait_sent(dmx_num, DMX_TIMEOUT_TICK);\n}\n```\n\nTo read from the DMX bus, two additional functions are provided. The function `dmx_receive()` waits until a new packet has been received. The function `dmx_read()` reads the data from the driver buffer into an array so that it can be processed. If it is desired to process RDM requests, the function `rdm_send_response()` may be used.\n\n```c\ndmx_packet_t packet;\nwhile (true) {\n  int size = dmx_receive(dmx_num, \u0026packet, DMX_TIMEOUT_TICK);\n  if (size \u003e 0) {\n    dmx_read(dmx_num, data, size);\n\n    // Optionally handle RDM requests\n    if (packet.is_rdm) {\n      rdm_send_response(dmx_num);\n    }\n\n    // Process data here...\n  }\n\n  // Do other work here...\n\n}\n```\n\nThat's it! For more detailed information on how this library works including details on RDM, keep reading.\n\n## What is DMX?\n\nDMX is a unidirectional communication protocol used primarily in the entertainment industry to control lighting and stage equipment. DMX is transmitted as a continuous stream of packets using half-duplex RS-485 signalling with a standard UART port. DMX devices are typically connected using XLR5 in a daisy-chain configuration but other connectors such as XLR3 are common in consumer products.\n\nEach DMX packet begins with a high-to-low transition called the break, followed by a low-to-high transition called the mark-after-break, followed by an eight-bit byte. This first byte is called the start code. The start-of-packet break, mark-after-break, and start code is called the reset sequence. After the reset sequence, a packet of up to 512 data bytes may be sent.\n\nDMX imposes very strict timing requirements to allow for backwards compatibility with older lighting equipment. Frame rates may range from 1fps to up to approximately 830fps. A typical DMX controller transmits packets between approximately 25fps to 44fps. DMX receivers and transmitters have different timing requirements which must be adhered to carefully to ensure commands are processed.\n\nToday, DMX often struggles to keep up with the demands of the latest hardware. Its low data rate and small packet size sees it losing market popularity over more capable protocols. However its simplicity and robustness often makes it the first choice for small scale projects.\n\nFor in-depth information on DMX, see the [E1.11 standards document](https://tsp.esta.org/tsp/documents/docs/ANSI-ESTA_E1-11_2008R2018.pdf).\n\n### What is RDM?\n\nRDM stands for Remote Device Management. It is an extension to the DMX protocol to allow intelligent, bidirectional communication between devices from multiple manufacturers utilizing a modified DMX data link. RDM permits a console or other controlling device to discover and then configure, monitor, and manage intermediate and end-devices connected through a DMX network.\n\nWhen RDM capable devices must be configured but are inconveniently out of reach, it is said that RDM is \"faster than the ladder.\" Instead of needing to climb a ladder to reach a device users are able to make changes to device settings from their DMX controller.\n\nFor in-depth information on RDM, see the [E1.20 standards document](https://getdlight.com/media/kunena/attachments/42/ANSI_E1-20_2010.pdf).\n\n## DMX Basics\n\nIn a typical configuration, a DMX system consists of one DMX controller and up to 32 DMX fixtures per DMX port. A five-pin XLR cable, commonly known as a DMX cable, is connected to the DMX-out port of the DMX controller and into the the DMX-in port of the first DMX fixture. Each subsequent fixture is connected by a DMX cable between the DMX-out port of the previous fixture and the DMX-in port of the next fixture. A DMX terminator may be connected to the DMX-out port of the final fixture. This is only required when using RDM but can be helpful to ensure DMX signal stability when connecting more than 32 fixtures or for long runs of DMX cable.\n\n### Addresses and the Start Code\n\nDMX addresses are needed for DMX controllers to communicate with fixtures. A fixture's address can be set between 1 and 512 (inclusive) by setting the fixture's DIP switches or by using the fixture's built-in display. DMX addresses correspond to DMX slots in a DMX packet. DMX address one is mapped to DMX packet slot one. Each DMX slot is an 8-bit number. A slot's minimum value is 0 and its maximum value is 255. To control a DMX dimmer set to DMX address five, DMX packet slot five must be written. To set the DMX dimmer to full intensity, slot five must be written to value 255. To set the dimmer to zero intensity, slot five must be written to value 0. Setting slot five to any value in between will dim the intensity appropriately.\n\nSlot zero in the DMX packet is called the DMX start code. The start code informs DMX fixtures what type of packet is being sent. Standard DMX packets use a start code of `0x00`, also called the null start code. DMX fixtures will not respond to DMX packets unless the packet begins with a null start code! A list of the other supported start codes can be found in the [DMX start codes](#dmx-start-codes) section.\n\n### Footprints\n\nMany DMX fixtures support multiple controllable DMX parameters. Fixtures that support multiple parameters use multiple DMX addresses. An RGB LED fixture is a common example of a multi-parameter fixture. It uses three parameters: red, green, and blue. In this example, this fixture would therefore use three, consecutive DMX addresses. This is called the fixture's DMX footprint. The red, green, and blue parameters of an RGB LED fixture set to DMX address five can be controlled by writing to slots five, six, and seven, respectively. A fixture's DMX footprint can be found by reading its user manual. It is possible to address multiple DMX fixtures so that their DMX footprints overlap but this is uncommon.\n\nMulti-parameter fixtures may support multiple footprints. On a fixture that supports multiple footprints, only one footprint can be active at a time. Larger footprints may be used to provide finer control of the fixture's DMX parameters. Conversely, smaller footprints may be used when finer control of a fixture is not needed. Instructions to change a fixture's active footprint may be found by consulting its user manual.\n\nA fixture which supports multiple footprints is said to possess multiple personalities. Each DMX personality may support a different footprint.\n\n### Universes\n\nWhen more than 512 DMX addresses are used, it is required to use multiple DMX ports. Each DMX port is called a DMX universe. DMX fixtures can be uniquely identified by their DMX universe and address numbers. A common way to notate this is by separating the universe and address with a `/`. Therefore `3/475` represents universe three, address 475. It is important to understand that DMX fixtures are not aware of the concept of a universe. A fixture set to DMX address one will respond to writes to slot one on whichever universe to which it is connected.\n\n## RDM Basics\n\nCompared to DMX, RDM is a fairly complex protocol which utilizes different packet and data types depending on the information that is being requested. This library attempts to abstract away many of the details of the RDM implementation to facilitate code ease-of-use while still providing a powerful feature set. The following sections are intended to provide a introduction to RDM as it pertains to this library. For a more comprehensive introduction of RDM, see the [E1.20 standards document](https://getdlight.com/media/kunena/attachments/42/ANSI_E1-20_2010.pdf).\n\n### Unique IDs\n\nIn an RDM network, there is one controller device and several responder devices. Each device in the network has a Unique ID (UID) which uniquely identifies itself against others in the network. If an RDM device has multiple DMX ports, it may possess multiple UIDs; one for each DMX port. UIDs are 48-bits long. The most-significant 16-bits are a device's manufacturer ID. Devices made by the same manufacturer have the same most-significant 16-bits. The remaining, 32 least-significant-bits are a device's device ID. Readers may draw a reasonable comparison to MAC addresses in IP networking equipment. Every IP capable device has at least one MAC address and if they have multiple network interfaces they may have multiple MAC addresses. Similarly, the most significant bits in a MAC address identify a network interface's manufacturer.\n\nUIDs are represented in text by displaying the UID in hexadecimal and by separating the manufacturer ID from the device ID with a `:`. If a device has a manufacturer ID with a value of `0xabcd` and device ID with a value of `0x12345678`, its full UID would be displayed as `abcd:12345678`.\n\nWhen a controller device composes an RDM request, it must be addressed using a destination UID. The recipient of a request may be a single device, by using the device's UID, or multiple devices, by using a broadcast UID. Broadcast UIDs can be addressed to every device of a specific manufacturer or to all devices on the RDM network. To send a manufacturer broadcast, the destination UID's manufacturer ID must match the manufacturer ID of the desired manufacturer, and the device ID must be `ffffffff`. To broadcast to every device with the manufacturer ID of `05e0`, the UID must be set to `05e0:ffffffff`. The UID used to broadcast to all devices on the RDM network is `ffff:ffffffff`.\n\nThe lowest possible UID is `0001:00000000` and the highest possible UID is `ffff:fffffffe`. In practice the manufacturer ID `ffff` is not permitted so real-world RDM devices would never possess a UID higher than `7fff:fffffffe`. This library represents the maximum UID with the constant `RDM_UID_MAX`.\n\nOrganizations may apply for a unique manufacturer ID by contacting ESTA. The instructions to do so and a list of registered manufacturer IDs can be found [here](https://tsp.esta.org/tsp/working_groups/CP/mfctrIDs.php). This software library is registered and listed with the manufacturer ID of `05e0`. Users of this library may use this manufacturer ID for their devices.\n\nIn this library, UIDs are represented with the `rdm_uid_t` type. The macro `rdm_uid_broadcast_man()` can be used to create a UID which broadcasts to the desired manufacturer ID and the constant `RDM_UID_BROADCAST_ALL` can be used to broadcast to all devices on the RDM network.\n\n### Sub-devices\n\nEach RDM device may support up to 512 sub-devices. An example of a device that may support sub-devices is a dimmer rack which possesses multiple dimmers. Requests may be addressed to a specific dimmer in the dimmer rack by addressing the dimmer rack's UID, and specifying a sub-device number to target the appropriate dimmer.\n\nThe sub-device number which represents the root device is `0x0000`. A request may also be addressed to all sub-devices of a root device by using the sub-device number `0xffff`. The constants `RDM_SUB_DEVICE_ROOT` and `RDM_SUB_DEVICE_ALL` are provided to improve code readability.\n\nA root device and its sub-devices may support different RDM parameters, but each sub-device within a root device must support the same parameters as each other.\n\n### Parameters\n\nRDM requests must be able to fetch and update parameters. The RDM standard specifies more than 50 different Parameter IDs (PIDs) which a device may support. The standard also specifies that manufacturers may define custom PIDs for their devices.\n\nMost PIDs can be either GET or SET if the responding device supports the requested PID. Some PIDs may support GET but do not support SET, and vice versa. Some PIDs may support both GET and SET. Three PIDs cannot be GET nor SET. These three PIDs are used for the RDM discovery algorithm. They are `DISC_UNIQUE_BRANCH`, `DISC_MUTE`, and `DISC_UN_MUTE`. This library provides constants for each PID. Each PID in this library is prefixed with `RDM_PID_`. Therefore, `DISC_UNIQUE_BRANCH` would become `RDM_PID_DISC_UNIQUE_BRANCH`. This document will refer to PIDs by their prefixed names for consistency of documentation.\n\nRDM specifies that every device (but not its sub-devices necessarily) must support a specific set of PIDs to ensure proper communication between devices. The list of the supported and the required PIDs can be found in the [appendix](#parameter-ids).\n\nGET requests may not be sent to all sub-devices of a root device. It is therefore not permitted to send a GET request to `RDM_SUB_DEVICE_ALL`.\n\n### Discovery\n\nWhen making RDM requests it is typically needed (but not required) to discover the UIDs of the devices on the RDM network. The discovery process begins with the controller device broadcasting an `RDM_PID_DISC_UNIQUE_BRANCH` command to all devices. The data included in this request consist of an address space defined by a UID lower bound and UID upper bound. Responding devices respond to `RDM_PID_DISC_UNIQUE_BRANCH` requests if their UID is greater-than-or-equal to the lower bound and less-than-or-equal to the upper bound. When multiple devices respond at the same time, data collisions can occur. When a data collision occurs, the controller divides the address space in two. An `RDM_PID_DISC_UNIQUE_BRANCH` request is sent to each new address space. This is repeated until a single device is found within an address space.\n\nWhen a single device is found within an address space, that device is sent an `RDM_PID_DISC_MUTE` request to mute its response to future `RDM_PID_DISC_UNIQUE_BRANCH` requests. When responding to `RDM_PID_DISC_MUTE` requests, devices that have multiple RDM ports return a binding UID which represents its primary UID.\n\nSome RDM devices act as proxy devices. A proxy device is any inline device that acts as an agent or representative for one or more devices. A proxy device shall respond to all controller messages on behalf of the devices it represents as if it is the represented device. If a device is acting as a proxy device or if it is proxied by another device, it will indicate so in its response to `RDM_PID_DISC_MUTE` and `RDM_PID_DISC_UN_MUTE` requests.\n\nDiscovery should be performed periodically as discovered devices may be removed from the RDM network or new devices may be added. Before restarting the discovery algorithm, a `RDM_PID_DISC_UN_MUTE` request should be broadcast to all devices in order to detect if devices were removed from the RDM network.\n\n### Responses\n\nResponding devices shall respond to requests only if the request was a non-broadcast request. Responding devices may respond to requests with the following response types:\n\n- `RDM_RESPONSE_TYPE_ACK` indicates that the responder has correctly received the controller message and is acting upon the request.\n- `RDM_RESPONSE_TYPE_ACK_OVERFLOW` indicates that the responder has correctly received the controller message and is acting upon the request, but there is more response data available than will fit in a single response packet. To receive the remaining information, controllers are able to send repeated requests to the same PID until the remaining information can fit in a single message.\n- `RDM_RESPONSE_TYPE_ACK_TIMER` indicates that the responder is unable to supply the requested GET information or SET confirmation within the required response time. When sending this response, responding devices include an estimated response time that must elapse before the responder can provide the required information.\n- `RDM_RESPONSE_TYPE_NACK_REASON` indicates that the responder is unable to reply with the requested GET information or unable to process the specified SET command. Responding devices must include a NACK reason code in their response. NACK reason codes are enumerated in the [appendix](#nack-reason-codes).\n\nTwo additional response types are defined for this library. These response types are included to assist users with processing RDM data.\n\n- `RDM_RESPONSE_TYPE_NONE` indicates that no response was received.\n- `RDM_RESPONSE_TYPE_INVALID` indicates that a response was received, but the response was invalid. This can occur for several reasons including an invalid checksum, or an invalid packet format.\n\nResponders must respond to every non-broadcast RDM request as well as every broadcast `RDM_PID_DISC_UNIQUE_BRANCH` request if their RDM discovery is un-muted and if their UID falls within the request's address space. When responding to `RDM_PID_DISC_UNIQUE_BRANCH` requests, responders shall not send a DMX break and mark-after-break in order to improve discovery times and shall encode their response to reduce data loss during data collisions. The omission of the DMX break and mark-after-break is handled automatically by the DMX driver. Responders may only respond to `RDM_PID_DISC_UNIQUE_BRANCH`, `RDM_PID_DISC_MUTE`, and `RDM_PID_DISC_UN_MUTE` requests with `RDM_RESPONSE_TYPE_ACK`.\n\n## Configuring the DMX Port\n\nThe DMX driver’s functions identify each of the UART controllers using `dmx_port_t`. This identification is needed for all the following function calls.\n\n### Installing the Driver\n\nBefore any DMX functions may be called, the DMX driver must be installed. Install the driver by calling `dmx_driver_install()`. This function will allocate the necessary resources for the DMX driver. It instantiates the driver to default DMX timing. The following parameters are passed to this function:\n\n- The DMX port to use.\n- The DMX configuration to use. The macro `DMX_CONFIG_DEFAULT` can be used to declare a struct with the default configuration.\n- The DMX personalities that the device will use. This is an array of `dmx_personality_t`. If the device does not use any DMX slots this value can be `NULL`.\n- The personality count or 0 if the device does not use any DMX slots. The maximum number of personalities allowed is 255.\n\n```c\ndmx_config_t config = DMX_CONFIG_DEFAULT;\ndmx_personality_t personalities[] = {\n  {1, \"Single-channel Mode\"},  // Single-address DMX personality\n  {3, \"RGB\"},                  // Three-address RGB mode\n  {4, \"RGBW\"},                 // Four-address RGBW personality\n  {7, \"RGBW with Macros\"}      // RGBW with three additional macro parameters\n};\nconst int personality_count = 4;\ndmx_driver_install(DMX_NUM_1, \u0026config, personalities, personality_count);\n```\n\nThe `dmx_config_t` sets permanent configuration values within the DMX driver. These values are used to configure the DMX device and for the RDM responder. The fields in the `dmx_config_t` include:\n\n- `interrupt_flags` The interrupt allocation flags to use. The default value is `DMX_INTR_FLAGS_DEFAULT`.\n- `root_device_parameter_count` The number of parameters that the root device supports. This is the number of parameters that may be registered on the root device. The default value is `32`.\n- `sub_device_parameter_count` The number of parameters that the sub-devices support. This is the number of parameters that may be registered per sub-device. The default value is `0`.\n- `model_id` This field identifies the device model ID of the root device. This is an arbitrary value set by the user to uniquely identify different models of RDM devices made by a single manufacturer from one another. The default value is `0`.\n- `product_category` Devices shall report a product category based on the product's primary function. The product categories are enumerated in `product_category_t`. The default value is `RDM_PRODUCT_CATEGORY_FIXTURE`.\n- `software_version_id` This field indicates the software version ID for the device. The software version ID is a 32-bit value determined by the manufacturer. The default value is based on the current version of *esp_dmx*.\n- `software_version_label` This RDM parameter is used to get a descriptive ASCII text label for the device's operating software version. The descriptive text returned by this parameter is intended for display to the user. The default value is a string based on the current version of *esp_dmx*.\n- `queue_size_max` The maximum size of the RDM queue. Setting this value to 0 disables the RDM queue. The default value is `32`.\n\nThe `dmx_personality_t` type is a struct which contains two fields: `footprint` and `description`. The `footprint` field is the DMX footprint of the personality. This is the number of DMX slots which this footprint uses. The `description` field is a string which describes the purpose of the DMX personality. This field is used for RDM responses and may be up to 33 characters long including a null-terminator.\n\n```c\ndmx_config_t config = {\n  .interrupt_flags = DMX_INTR_FLAGS_DEFAULT,\n  .root_device_parameter_count = 32,\n  .sub_device_parameter_count = 0,\n  .model_id = 0,\n  .product_category = RDM_PRODUCT_CATEGORY_FIXTURE,\n  .software_version_id = ESP_DMX_VERSION_ID,\n  .software_version_label = ESP_DMX_VERSION_LABEL,\n  .queue_size_max = 32\n};\ndmx_driver_install(DMX_NUM_1, \u0026config, personalities, personality_count);\n```\n\n### Setting Communication Pins\n\nAfter the DMX driver is installed, users can configure the physical GPIO pins to which the DMX port will be connected. To do this, call the function `dmx_set_pin()` and specify which GPIO should be connected to the TX, RX, and RTS signals. If you want to keep a currently allocated pin to a specific signal, pass the macro `DMX_PIN_NO_CHANGE`. This macro should also be used if a pin isn't used.\n\n```c\n// Set TX: GPIO16 (port 2 default), RX: GPIO17 (port 2 default), RTS: GPIO21.\ndmx_set_pin(DMX_NUM_1, DMX_PIN_NO_CHANGE, DMX_PIN_NO_CHANGE, 21);\n```\n\n### Timing Configuration\n\nIn most situations it is not necessary to adjust the default timing of the DMX driver. Nevertheless, this library allows for individual configuration of the DMX baud rate, break, and mark-after-break for the DMX controller. These functions have no effect when receiving DMX; they only effect the baud rate, break, and mark-after-break when sending DMX or RDM. After the DMX driver has been installed, the following functions may be called.\n\n```c\ndmx_set_baud_rate(DMX_NUM_1, DMX_BAUD_RATE);     // Set DMX baud rate.\ndmx_set_break_len(DMX_NUM_1, DMX_BREAK_LEN_US);  // Set DMX break length.\ndmx_set_mab_len(DMX_NUM_1, DMX_MAB_LEN_US);      // Set DMX MAB length.\n```\n\nIf timing values that are not within the DMX specification are passed to these functions, the values will be clamped so that they are within DMX specification. Note that it is possible to set driver timing to be within DMX specification but not within RDM specification. Care must be used when using these functions to ensure that RDM capabilities are maintained.\n\nThe above functions each have `_get_` counterparts to retrieve the currently set DMX timing parameters.\n\n## Reading and Writing DMX\n\nDMX is a unidirectional protocol. This means that on the DMX bus only one device can transmit commands and many devices listen for commands. Therefore, this library permits either reading or writing to the bus but not both at once. If sending and receiving data concurrently is desired, users can use two UART ports and install a driver on each port.\n\n### Reading DMX\n\nReading may be performed synchronously or asynchronously from the DMX bus. It is typically desired to perform reads synchronously. This means that reads are only performed when a new DMX packet is received. This is ideal because it is not commonly desired to perform reads on the same data multiple times.\n\nTo read synchronously from the DMX bus the DMX driver must wait for a new packet. The blocking function `dmx_receive()` can be used for this purpose.\n\n```c\ndmx_packet_t packet;\n// Wait for a packet. Returns the size of the received packet or 0 on timeout.\nint packet_size = dmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK);\n```\n\nThe function `dmx_receive()` takes three arguments. The first argument is the `dmx_port_t` which identifies which DMX port to use. The second argument is a pointer to a `dmx_packet_t` struct. Data about the received packet is copied into the `dmx_packet_t` struct when a packet is received. This data includes:\n\n- `err` reports any errors that occurred while receiving the packet (see: [Error Handling](#error-handling)).\n- `sc` is the start code of the packet.\n- `size` is the size of the packet in bytes, including the DMX start code. This value will never be higher than `DMX_PACKET_SIZE`.\n- `is_rdm` evaluates to true if the packet is an RDM packet and if the RDM checksum is valid.\n\nUsing the `dmx_packet_t` struct is optional. If processing DMX or RDM packet data is not desired, users can pass `NULL` in place of a pointer to a `dmx_packet_t` struct.\n\nThe `dmx_receive()` function only returns a non-zero value when new data is received. Data is considered \"new\" when a DMX break is received. DMX data may also be considered \"new\" when an `RDM_PID_DISC_UNIQUE_BRANCH` response is received since these RDM responses are not sent with a DMX break.\n\nThe final argument to `dmx_receive()` is the amount of FreeRTOS ticks to block until the function times out. This library defines a constant, `DMX_TIMEOUT_TICK`, which is the length of time that must be waited until the DMX signal is considered lost according to DMX specification. According to DMX specification this constant is equivalent to 1250 milliseconds. If non-blocking behavior is desired, users should set this value to 0.\n\nAfter a packet is received, `dmx_read()` can be called to read the packet into a user buffer. It is recommended to check for DMX errors before reading data but it is not required.\n\n```c\nuint8_t data[DMX_PACKET_SIZE];\n\ndmx_packet_t packet;\nif (dmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK)) {\n\n  // Check that no errors occurred.\n  if (packet.err == DMX_OK) {\n    dmx_read(DMX_NUM_1, data, packet.size);\n  } else {\n    printf(\"An error occurred receiving DMX!\");\n  }\n\n} else {\n  printf(\"Timed out waiting for DMX.\");\n}\n```\n\nThe function `dmx_receive_num()` is provided to receive a specified number of DMX slots before returning. This function is identical to `dmx_receive()` except that it provides an additional argument which sets the number of slots to receive. This value is ignored when receiving RDM packets so that `dmx_receive()` and `dmx_receive_num()` will always receive full RDM packets.\n\n```c\ndmx_packet_t packet;\nint num_slots_to_receive = 96;\ndmx_receive_num(DMX_NUM_1, \u0026packet, num_slots_to_receive, DMX_TIMEOUT_TICK);\n```\n\nThe function `dmx_receive()` can be viewed as a wrapper for `dmx_receive_num()` where the number of slots to receive is equal to the packet size of the last DMX packet received. When the desired number of slots to receive is greater than the actual number of slots received (e.g. when waiting to receive 513 slots, but only 128 are received) the function will unblock upon receiving the DMX break for the subsequent packet and the `packet.err` will be set to `DMX_ERR_NOT_ENOUGH_SLOTS`.\n\nThere are two variations to the `dmx_read()` function. The function `dmx_read_offset()` is similar to `dmx_read()` but allows a small footprint of the entire DMX packet to be read.\n\n```c\nconst int size = 12;   // The size of this device's DMX footprint.\nconst int offset = 5;  // The start address of this device.\nuint8_t data[size];\n\n// Read slots 5 through 17. Returns the number of slots that were read.\nint num_slots_read = dmx_read_offset(DMX_NUM_1, offset, data, size);\n```\n\nLastly, `dmx_read_slot()` can be used to read a single slot of DMX data.\n\n```c\nconst int slot_num = 0;  // The slot to read. Slot 0 is the DMX start code!\n\n// Read slot 0. Returns the value of the desired slot or -1 on error.\nint value = dmx_read_slot(DMX_NUM_1, slot_num);\n```\n\n### DMX Sniffer\n\nThis library offers an option to measure DMX break and mark-after-break timings of received data packets. The sniffer is much more resource intensive than the default DMX driver, so it must be explicitly enabled by calling `dmx_sniffer_enable()`.\n\nThe DMX sniffer installs an edge-triggered interrupt on the specified GPIO pin. This library uses the ESP-IDF provided GPIO ISR which allows the use of individual interrupt handlers for specific GPIO interrupts. The interrupt handler works by iterating through each GPIO to determine if it triggered an interrupt and if so, it calls the appropriate handler.\n\nA quirk of the default ESP-IDF GPIO ISR is that lower GPIO numbers are processed earlier than higher GPIO numbers. It is recommended that the DMX read pin be shorted to a lower GPIO number in order to ensure that the DMX sniffer can run with low latency.\n\nIt is important to note that the sniffer requires a fast clock speed in order to maintain low latency. In order to guarantee accuracy of the sniffer, the ESP32 must be set to a CPU clock speed of at least 160MHz. This setting can be configured in `Kconfig` if the ESP-IDF is used.\n\nBefore enabling the sniffer tool, `gpio_install_isr_service()` must be called with the required DMX sniffer interrupt flags. The macro `DMX_SNIFFER_INTR_FLAGS_DEFAULT` can be used to provide the proper interrupt flags.\n\n```c\ngpio_install_isr_service(DMX_SNIFFER_INTR_FLAGS_DEFAULT);\n\nconst int sniffer_pin = 4; // Lowest exposed pin on the Feather breakout board.\ndmx_sniffer_enable(DMX_NUM_1, sniffer_pin);\n```\n\nBreak and mark-after-break timings are reported to the DMX sniffer when it is enabled. To read data from the DMX sniffer call `dmx_sniffer_get_data()` after a DMX packet is received to copy data into a `dmx_metadata_t` struct. If data is copied, the function will return `true`.\n\n```c\ndmx_packet_t packet;\nif (dmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK)) {\n  dmx_metadata_t metadata;\n  if (dmx_sniffer_get_data(DMX_NUM_1, \u0026metadata, DMX_TIMEOUT_TICK)) {\n    printf(\"The DMX break length was: %i\\n\", metadata.break_len);\n    printf(\"The DMX mark-after-break length was: %i\\n\", metadata.mab_len);\n  }\n}\n```\n\n### Writing DMX\n\nTo write to the DMX bus, `dmx_write()` can be called. This writes data to the DMX driver but it does not transmit a packet onto the bus. In order to transmit the data that was written, `dmx_send()` must be called.\n\n```c\nuint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };\n\n// Write the packet and send it out on the DMX bus.\nconst int num_bytes_to_write = DMX_PACKET_SIZE;\ndmx_write(DMX_NUM_1, data, num_bytes_to_write);\ndmx_send(DMX_NUM_1,);\n```\n\nIt takes a typical DMX packet approximately 22 milliseconds to send. During this time, it is possible to write new data to the DMX driver with `dmx_write()` if non-RDM data is being sent. To do so would result in an asynchronous write which may not be desired. To write data synchronously it is required to wait until the DMX packet is finished being sent. The function `dmx_wait_sent()` is used for this purpose.\n\n```c\nuint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };\n\nwhile (true) {\n  // Send the DMX packet.\n  dmx_send(DMX_NUM_1);\n\n  // Process the next DMX packet (while the previous is being sent) here.\n  for (int i = 1; i \u003c DMX_PACKET_SIZE; i++) {\n    data[i]++;  // Increment the value of each slot, excluding the start code.\n  }\n\n  // Wait until the packet is finished being sent before proceeding.\n  dmx_wait_sent(DMX_NUM_1, DMX_TIMEOUT_TICK);\n\n  // Now write the packet synchronously!\n  dmx_write(DMX_NUM_1, data, DMX_PACKET_SIZE);\n}\n```\n\nWhen sending DMX, the `dmx_send()` function sends the maximum number of slots allowed by the DMX standard. When an RDM packet is sent using `dmx_send()`, the DMX driver will automatically send only the slots which make up the RDM packet.\n\nTo send a specific number of DMX slots, the function `dmx_send_num()` may be used. The number of slots to send is ignored when sending RDM data.\n\n```c\nconst int num_bytes_to_send = 96;\ndmx_send_num(DMX_NUM_1, num_bytes_to_send);\n```\n\nAn offset of DMX slots can be written using `dmx_write_offset()` and individual DMX slots can be written using `dmx_write_slot()`. This behavior is similar to reading an offset of DMX slots or reading a single DMX slot using `dmx_read_offset()` and `dmx_read_slot()`, respectively.\n\n```c\nuint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };\n\n// Write slots 10 through 17 (inclusive)\nconst int offset = 10;\nconst size_t size = 7;\ndmx_write_offset(DMX_NUM_1, offset, data, size);\n\n// Set slot number 5 to value 127.\nconst int slot_num = 5;\nconst uint8_t value = 127;\ndmx_write_slot(DMX_NUM_1, slot_num, value);\n\n// Don't forget to call dmx_send()!\n```\n\n### DMX Parameters\n\nUpon installing the DMX driver, some parameter values are set which may be get or set by the user. These parameters include the current DMX personality, the personality count, the footprint of a specified personality, the description of a personality, and the DMX start address.\n\nGetting or setting the DMX start address can be done using `dmx_get_start_address()` and `dmx_set_start_address()`. When RDM is enabled, these functions behave similarly to `rdm_get_dmx_start_address()` and `rdm_set_dmx_start_address()`.\n\n```c\n// Get the DMX start address and increment it by one\nuint16_t dmx_start_address = dmx_get_start_address(DMX_NUM_1);\ndmx_start_address++;\nif (dmx_start_address \u003e= DMX_PACKET_SIZE_MAX) {\n  dmx_start_address = 1;  // Ensure DMX start address is within bounds\n}\ndmx_set_start_address(DMX_NUM_1, dmx_start_address);\n```\n\nPersonalities, the personality count, personality descriptions, and footprint sizes may be accessed with `dmx_get_current_personality()`, `dmx_set_current_personality()`, `dmx_get_personality_count()`, `dmx_get_personality_description()`, and `dmx_get_footprint()`. Personalities are indexed starting at one. There is no personality zero.\n\n```c\nconst uint8_t personality_count = dmx_get_personality_count(DMX_NUM_1);\nuint8_t current_personality = dmx_get_current_personality(DMX_NUM_1);\nif (current_personality \u003c personality_count) {\n  // Increment the personality.\n  current_personality++;\n  /* It is ok if current_personality == personality_count because personalities\n    start at 1, not 0! */\n\n  // Get and print the new personality description and footprint.\n  const char *desc = dmx_get_personality_description(DMX_NUM_1, \n                                                     current_personality);\n  uint16_t footprint = dmx_get_footprint(DMX_NUM_1, current_personality);\n  printf(\"Setting the current personality to %i: '%s'\\n\", current_personality,\n         desc);\n  printf(\"Personality %i has a footprint of %i\\n\", current_personality,\n         footprint);\n  \n  dmx_set_current_personality(DMX_NUM_1, current_personality);\n}\n```\n\n## Reading and Writing RDM\n\nUsing only the functions listed above it is possible to send and receive RDM packets. When an RDM packet is written using `dmx_write()` the DMX driver will respond accordingly and ensure that RDM timing requirements are met. For example, calls to `dmx_send()` and `dmx_send_num()` typically send a DMX break and mark-after-break when sending a DMX packet with a null start code. When sending an RDM discovery response packet the DMX driver automatically removes the DMX break and mark-after-break which is required per the RDM standard. Sending RDM responses with `dmx_send()` or `dmx_send_num()` may also fail when the DMX driver has detected that the RDM response timeout has already elapsed. This is done to reduce the number of data collisions on the RDM bus and keeps the RDM bus operating properly.\n\n```c\n// This is a hard-coded discovery response packet.\nconst uint8_t discovery_response[] = {\n  0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xaa, 0xaf, 0x55, 0xea, 0xf5, 0xba, \n  0x57, 0xbb, 0xdd, 0xbf, 0x55, 0xba, 0xdf, 0xaa, 0x5d, 0xbb, 0x7d \n};\ndmx_write(DMX_NUM_1, discovery_response, sizeof(discovery_response));\n\n// This function will not send a DMX break or mark-after-break \ndmx_send(DMX_NUM_1);\n```\n\nLikewise, the `dmx_receive()` functions behave contextually when receiving DMX or RDM packets. When receiving DMX, calls to `dmx_receive()` and `dmx_receive_num()` will timeout according to the timeout value provided, such as `DMX_TIMEOUT_TICK`. When receiving RDM packets, the DMX driver may timeout much more quickly than the provided timeout value as the RDM bus turnaround times are much shorter than DMX.\n\n```c\n// This is a hard-coded GET DEVICE_INFO request.\nconst uint8_t get_device_info[] = {\n  0xcc, 0x01, 0x18, 0x3b, 0x10, 0x44, 0xc0, 0x6f, 0xbf, 0x05, 0xe0, 0x12, 0x99,\n  0x15, 0x9a, 0x14, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x60, 0x00, 0x06, 0x38\n};\ndmx_write(DMX_NUM_1, get_device_info, sizeof(get_device_info));\ndmx_send(DMX_NUM_1, sizeof(get_device_info));\n\ndmx_packet_t packet;\n\n// This function will unblock early because it is expecting a reply!\ndmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK);  // Unblocks in 3ms\n```\n\nBecause writing RDM requests and responses in this way can be cumbersome, this library provides functions for sending RDM requests and responses. They can be included by adding `#include \"rdm/controller.h\"` for requests and `#include \"rdm/responder.h\"` for responses.\n\n### RDM Requests\n\nThis library supports the required PIDs specified in the RDM standard. Request functions in this library are named using the prefix `rdm_send_`, whether the request is a GET or a SET, and the parameter name. To GET the `RDM_PID_DEVICE_INFO` of a responder device, users can call `rdm_send_get_device_info()`. To SET a device's `RDM_PID_DMX_START_ADDRESS`, users can call `rdm_send_set_dmx_start_address()`. All GET request functions return `true` if an `RDM_RESPONSE_TYPE_ACK` was received or `false` if an `RDM_RESPONSE_TYPE_ACK` was not received. All SET request functions return the number of bytes received in the RDM parameter data if an `RDM_RESPONSE_TYPE_ACK` was received or 0 otherwise. For example `rdm_send_get_software_version_label()` will return the number of characters in the software version label that was received in the RDM response packet.\n\nIn addition to the DMX port number most RDM request functions use at least two arguments to determine where the RDM request should be directed. These arguments are `dest_uid`, the destination UID and `sub_device` the RDM sub-device which should receive the request.\n\nWhen printing UIDs to the terminal, the macros `UIDSTR` and `UID2STR()` can be used in printf-like functions.\n\n```c\nrdm_uid_t dest_uid = {0x05e0, 0x44c06fbf};  // The destination UID\nrdm_sub_device_t sub_device = RDM_SUB_DEVICE_ROOT;\nrdm_ack_t ack;  // Stores response information\n\nrdm_device_info_t device_info;  // Stores the response parameter data.\nif (rdm_send_get_device_info(DMX_NUM_1, \u0026dest_uid, sub_device, \u0026device_info,\n                             \u0026ack)) {\n  printf(\"Successfully received device info from \" UIDSTR \"!\\n\",\n          UID2STR(ack.src_uid));\n}\n\nconst uint16_t new_address = 123;  // The new RDM_PID_DMX_START_ADDRESS to send.\nif (rdm_send_set_dmx_start_address(DMX_NUM_1, \u0026dest_uid, sub_device, \n                                   new_address, \u0026ack)) {\n  printf(\"Device \" UIDSTR \" has been set to DMX address %i.\\n\",\n          UID2STR(dest_uid), new_address);\n}\n```\n\nResponse information from requests is read into a `rdm_ack_t` pointer which is provided by the user. Users can use this type to ensure that requests were successful and, if they are not successful, handle errors. The `rdm_ack_t` type contains the following fields:\n\n- `err` is set to a non-zero error value if an error occurred reading DMX data. This field only indicates if an error occurred reading raw DMX data. It does not indicate if an invalid RDM packet was received. More information on error handling can be found in the [Error Handling](#error-handling) section.\n- `size` is the size of the received packet, including start code, RDM sub-start code, and checksum.\n- `src_uid` is the UID of the device originating the response packet.\n- `pid` is the PID of the response packet. This is typically the same as the PID which was sent in the request, but may differ for some requests.\n- `type` is the type of the RDM response received. It can be any of the RDM response types enumerated in [Response Types](#response-types).\n- `message_count` is used by an RDM responder to indicate that additional data is now available for collection by a controller.\n\nThe remaining field is a union which should be read depending on the value in `type`.\n\n- `pdl` should be read if `type` evaluates to `RDM_RESPONSE_TYPE_ACK`. It describes the size of the RDM parameter data that was received.\n- `timer` should be read if `type` evaluates to `RDM_RESPONSE_TYPE_TIMER`. It describes the number of FreeRTOS ticks that must elapse before the RDM responder will be ready to process the request.\n- `nack_reason` should be read if `type` evaluates to `RDM_RESPONSE_TYPE_NACK_REASON`. It describes the NACK reason code that was received from the RDM responder.\n\n### Discovering Devices\n\nThis library provides two functions for performing full RDM discovery. The function `rdm_discover_devices_simple()` is provided as a simple implementation of the discovery algorithm which takes a pointer to an array of UIDs to store discovered UIDs and returns the number of UIDs found.\n\n```c\nconst int array_size = 10;\nrdm_uid_t uids[array_size];\n\n// This function blocks and may take some time to complete!\nint num_uids = rdm_discover_devices_simple(DMX_NUM_1, uids, array_size);\n\nprintf(\"Discovery found %i UIDs!\\n\", num_uids);\n```\n\nDiscovery can take several seconds to complete. Users may want to perform an action, such as update a progress bar, whenever a new UID is found. When this is desired, the function `rdm_discover_with_callback()` may be used to specify a callback function which is called when a new UID is discovered.\n\n`RDM_PID_DISC_UNIQUE_BRANCH` requests support neither GET nor SET. This PID request can be accessed with the function `rdm_send_disc_unique_branch()`. `RDM_PID_DISC_UNIQUE_BRANCH` requests may only be sent to the root device, and may only be addressed to all devices on the RDM network. Therefore, the `dest_uid` and `sub_device` arguments are not provided for this function.\n\n```c\nrdm_ack_t ack;\n\n// Define the address space within which devices will be discovered.\nconst rdm_disc_unique_branch_t branch = {\n  .upper_bound = RDM_UID_MAX,\n  .lower_bound = 0  // Set to 0000:00000000\n};\n\nrdm_send_disc_unique_branch(DMX_NUM_1, \u0026branch, \u0026ack);\nif (ack.size \u003e 0) {\n  // Got a response!\n  if (ack.type == RDM_RESPONSE_TYPE_ACK) {\n    // Only one device was found - print its UID.\n    printf(\"Found the UID \" UIDSTR \".\\n\", UID2STR(ack.src_uid));\n  } else if (ack.type == RDM_RESPONSE_TYPE_INVALID) {\n    // The checksum was invalid indicating a data collision occurred.\n    printf(\"Multiple devices detected within this address space!\\n\");\n\n    // Branch the address space here...\n\n  }\n} else {\n  // No response was received - stop searching this address space.\n  printf(\"No RDM devices were discovered in this address space.\\n\");\n}\n```\n\n`RDM_PID_DISC_MUTE` and `RDM_PID_DISC_UN_MUTE` similarly do not support GET nor SET. Devices may be muted and un-muted by using the functions `rdm_send_disc_mute()` and `rdm_send_disc_un_mute()`. These requests may be sent to any destination UID but may only be sent to the root device. The `dest_uid` argument is provided, but `sub_device` is not. `RDM_PID_DISC_MUTE` and `RDM_PID_DISC_UN_MUTE` requests receive the same response data from responders. Therefore `rdm_disc_mute_t` can be used to store parameter data from responder devices for both requests.\n\n```c\nrdm_uid_t dest_uid = RDM_UID_BROADCAST_ALL;\nrdm_ack_t ack;\n\nrdm_disc_mute_t mute;  // Stores the response parameter data.\n\nrdm_send_disc_un_mute(DMX_NUM_1, \u0026dest_uid, \u0026mute, \u0026ack);\nif (ack.size \u003e 0) {\n  /* This code will never run because the RDM controller does not receive a\n    response from RDM responders when the destination UID is a broadcast UID.\n    Therefore its return value can be ignored and the function can be passed\n    NULL instead of an rdm_ack_t pointer or an rdm_disc_mute_t pointer. */\n}\n```\n\n### RDM Responder\n\nAn RDM responder must respond to every non-discovery, non-broadcast packet addressed to it. When a responder receives a `RDM_PID_DISC_UNIQUE_BRANCH` packet, it must respond to the packet if the responder's UID falls within the request's address space and if the responder is un-muted.\n\nThe DMX driver will parse RDM requests and send responses within the `rdm_send_response()` function. It is therefore required for all RDM responders to receive RDM requests with `dmx_receive()` or `dmx_receive_num()` and for responses to be sent with `rdm_send_response()`. If `rdm_send_response()` is not called, an RDM response will not be sent. If it is not desired for devices to respond to RDM requests the `rdm_send_response()` function may be omitted. To ensure responder devices are RDM compliant, users should call `rdm_send_response()` after receiving every RDM request.\n\n```c\ndmx_packet_t packet;\nif (dmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK)) {\n  if (packet.is_rdm) {\n    rdm_send_response(DMX_NUM_1);  // Only sends responses to relevant requests\n  }\n}\n```\n\nRDM imposes strict timing requirements on RDM responders. Responders must typically respond to RDM requests within approximately 3 milliseconds. It is important to call `rdm_send_response()` quickly after receiving new RDM data. Users are discouraged from calling lengthy functions (such as printing to the terminal) between calls to `dmx_receive()` and `rdm_send_response()`.\n\n```c\ndmx_packet_t packet;\nif (dmx_receive(DMX_NUM_1, \u0026packet, DMX_TIMEOUT_TICK)) {\n\n  // Caution! Printing log messages may take too long!\n  printf(\"A DMX packet has been received!\");\n\n  if (packet.is_rdm) {\n    rdm_send_response(DMX_NUM_1);  // Only sends responses to relevant requests\n  }\n}\n```\n\nRDM parameters can be registered with the DMX driver using functions prefixed with `rdm_register_`. The parameter `RDM_PID_DMX_START_ADDRESS` may therefore be registered with `rdm_register_dmx_start_address()`. Parameter data is owned and initialized by the DMX driver, but users may set the initial value for some parameters using the arguments to the `rdm_register_` functions.\n\nRDM parameters which support GET but do not support SET generally allow users to set the parameter's initial value as the second argument of the `rdm_register_` function. The initial value is set the first time the `rdm_register_` function is called and then the initial value argument is subsequently ignored and may be left `NULL`. RDM parameters which support GET and SET will generally be set to a predefined initial value upon registration and must be manually changed using their corresponding `rdm_set_` function.\n\nThe `rdm_register_` functions allow allow users to attach callback functions to PIDs. When a valid request for a parameter is received, the DMX driver will call the callback function after a request is processed. When a callback is called it does not necessarily mean that a response packet has been sent.\n\n```c\nvoid custom_callback(dmx_port_t dmx_num, rdm_header_t *request,\n                     rdm_header_t *response, void *context) {\n  if (request-\u003epid == RDM_PID_SOFTWARE_VERSION_LABEL) {\n    printf(\"A RDM_PID_SOFTWARE_VERSION_LABEL request was received!\\n\");\n  }\n}\n```\n\nThe arguments in the callback function reflect the RDM header received in the RDM request and the RDM header sent in the response. The DMX port number and a user context is also provided.\n\n```c\nvoid *context = NULL;  // Context not needed for the above callback \nconst char *new_software_label = \"My Custom Software\";\nif (rdm_register_software_version_label(DMX_NUM_1, new_software_label, \n                                        custom_callback, context)) {\n  printf(\"A new software version label has been registered!\\n\");\n}\n```\n\nIf a request for a PID that is not registered is received, the DMX driver will automatically respond with an `RDM_RESPONSE_NACK_REASON` response citing `RDM_NR_UNKNOWN_PID`. Registering a parameters which is already defined will overwrite the previously registered callback, but not the initial parameter value. Parameters which are registered cannot be unregistered.\n\nThe RDM standard defines several parameter responses that are required by all RDM compliant responders. These functions are automatically registered when the DMX driver is installed. This is needed to ensure that RDM responders created with this library are compliant with the RDM specification. The following parameters are required per the RDM specification and are therefore automatically registered when installing the DMX driver:\n\n- `RDM_PID_DISC_UNIQUE_BRANCH`\n- `RDM_PID_DISC_MUTE`\n- `RDM_PID_DISC_UN_MUTE`\n- `RDM_PID_DEVICE_INFO`\n- `RDM_PID_SOFTWARE_VERSION_LABEL`\n- `RDM_PID_IDENTIFY_DEVICE`\n- `RDM_PID_DMX_START_ADDRESS` if the device uses a DMX slot.\n- `RDM_PID_SUPPORTED_PARAMETERS` if supporting parameters beyond the minimum required set.\n- `RDM_PID_PARAMETER_DESCRIPTION` if supporting manufacturer-specific parameters.\n\nThe following parameters are not required by the RDM specification but are automatically registered when installing the DMX driver. Parameters are registered in the following order, if there is parameter space available on the DMX driver:\n\n- `RDM_PID_QUEUED_MESSAGE` if specified in the `dmx_config_t`.\n- `RDM_PID_MANUFACTURER_LABEL`\n- `RDM_PID_DMX_PERSONALITY` if the device uses a DMX slot.\n- `RDM_PID_DMX_PERSONALITY_DESCRIPTION` if the device uses a DMX slot.\n- `RDM_PID_DEVICE_LABEL`\n\nParameters which are registered may be get or set using getter and setter functions. Parameters which support the `RDM_CC_GET_COMMAND` command class have a getter function prefixed prefixed with `rdm_get_` and parameters which support `RDM_CC_SET_COMMAND` have a setter function prefixed with `rdm_set_`. Setter functions return `true` if the value was successfully set. Getter functions return the size of the parameter data in bytes or zero on failure.\n\nSome parameters, such as `RDM_PID_DMX_START_ADDRESS` are copied to non-volatile storage to ensure the values are saved after the ESP32 is power-cycled. The values are copied to non-volatile storage when set using the parameter's `rdm_set_` function or after receiving a valid SET request.\n\n```c\nuint16_t dmx_start_address;\nif (rdm_get_dmx_start_address(DMX_NUM_1, \u0026dmx_start_address) == 0) {\n  printf(\"An error occurred getting the DMX start address.\\n\");\n}\n\ndmx_start_address = 123;\nif (!rdm_set_dmx_start_address(DMX_NUM_1, dmx_start_address)) {\n  printf(\"An error occurred setting the DMX start address.\\n\");\n}\n```\n\n## Error Handling\n\nOn rare occasions, DMX packets can become corrupted. Errors are typically detected upon initially connecting to an active DMX bus but are resolved on receiving the next packet. Errors can be checked by reading the error code from the `dmx_packet_t` struct. The error types are as follows:\n\n- `DMX_OK` indicates data was read successfully.\n- `DMX_ERR_TIMEOUT` indicates that the driver timed out waiting for a packet.\n- `DMX_ERR_IMPROPER_SLOT` occurs when the DMX driver detects missing stop bits. If this condition occurs, the driver shall discard the improperly framed slot data and all following slots in the packet. When this error is reported the `dmx_packet_t` size can be read to determine at which slot the error occurred.\n- `DMX_ERR_UART_OVERFLOW` occurs when the ESP32 hardware overflows resulting in loss of data.\n- `DMX_ERR_NOT_ENOUGH_SLOTS` occurs when the number of slots received is less than the number desired in the call to `dmx_receive_num()`.\n\n```c\nuint8_t data[DMX_PACKET_SIZE];\n\nint num_slots = DMX_PACKET_SIZE;\ndmx_packet_t packet;\nwhile (true) {\n  if (dmx_receive_num(DMX_NUM_1, \u0026packet, num_slots, DMX_TIMEOUT_TICK)) {\n    switch (packet.err) {\n      case DMX_OK:\n        printf(\"Received packet with start code: %02X and size: %i.\\n\",\n          packet.sc, packet.size);\n        // Data is OK. Now read the packet into the buffer.\n        dmx_read(DMX_NUM_1, data, packet.size);\n        break;\n      \n      case DMX_ERR_TIMEOUT:\n        printf(\"The driver timed out waiting for the packet.\\n\");\n        /* If the provided timeout was less than DMX_TIMEOUT_TICK, it may be\n          worthwhile to call dmx_receive() again to see if the packet could be\n          received. */\n        break;\n\n      case DMX_ERR_IMPROPER_SLOT:\n        printf(\"Received malformed byte at slot %i.\\n\", packet.size);\n        /* A slot in the packet is malformed. Data can be recovered up until \n          packet.size. */\n        break;\n\n      case DMX_ERR_UART_OVERFLOW:\n        printf(\"The DMX port overflowed.\\n\");\n        /* The ESP32 UART overflowed. This could occur if the DMX ISR is being\n          constantly preempted. */\n        break;\n      \n      case DMX_ERR_NOT_ENOUGH_SLOTS:\n        printf(\"DMX packet size is too small. %i expected, %i received.\\n\",\n               num_slots, packet.size);\n        /* The packet was smaller than expected. This only occurs when receiving\n          DMX data. This error will not occur when receiving RDM packets.*/\n        num_slots = packet.size;  // Update expected packet size\n        break;\n    }\n  } else {\n    printf(\"Lost DMX signal.\\n\");\n    // A packet hasn't been received in DMX_TIMEOUT_TICK ticks.\n\n    // Handle packet timeout here...\n  }\n}\n```\n\nWhen reading RDM packets, the `packet.err` field is copied into the `rdm_ack_t` type. It should be noted that RDM packet errors are not reported as errors. The `err` field only reports errors in the processing of raw DMX data. If an invalid RDM packet is received, it will be reported in the `type` field of `rdm_ack_t`. Invalid RDM packets will be reported as `RDM_RESPONSE_TYPE_INVALID`.\n\n### Timing Macros\n\nIt should be noted that this library does not automatically check for DMX timing errors. This library does provide macros to assist with timing error checking, but it is left to the user to implement such measures. DMX and RDM each have their own timing requirements so macros for checking DMX and RDM are both provided. The following macros can be used to assist with timing error checking.\n\n- `dmx_baud_rate_is_valid()` evaluates to true if the baud rate is valid for DMX.\n- `dmx_break_len_is_valid()` evaluates to true if the DMX break duration is valid.\n- `dmx_mab_len_is_valid()` evaluates to true if the DMX mark-after-break duration is valid.\n- `rdm_baud_rate_is_valid()` evaluates to true if the baud rate is valid for RDM.\n- `rdm_break_len_is_valid()` evaluates to true if the RDM break duration is valid.\n- `rdm_mab_len_is_valid()` evaluates to true if the RDM mark-after-break duration is valid.\n\nDMX and RDM specify different timing requirements for receivers and transmitters. This library attempts to simplify error checking by combining timing requirements for receiving and transmitting. Therefore there are only the above six timing error checking macros instead of six macros each for receiving and transmitting.\n\n### DMX Start Codes\n\nThis library offers the following macro constants for use as DMX start codes. More information about each start code can be found in the DMX standards document or in [dmx/include/types.h](src/dmx/include/types.h).\n\n- `DMX_SC` is the standard DMX null start code.\n- `RDM_SC` is the standard Remote Device Management start code.\n- `DMX_TEXT_SC` is the ASCII text start code.\n- `DMX_TEST_SC` is the test packet start code.\n- `DMX_UTF8_SC` is the UTF-8 text packet start code.\n- `DMX_ORG_ID_SC` is the organization/manufacturer ID start code.\n- `DMX_SIP_SC` is the System Information Packet start code.\n\nAdditional macro constants include the following:\n\n- `RDM_SUB_SC` is the sub-start code for Remote Device Management. It is the first byte received after the RDM start code.\n- `RDM_PREAMBLE` is not considered a start code but is often the first byte received in an RDM discovery response packet.\n- `RDM_DELIMITER` is not considered a start code but is the delimiter byte received at the end of an RDM discovery response preamble.\n\nSome start codes are considered invalid and should not be used in a DMX packet. The validity of the start code can be checked using the macro `dmx_start_code_is_valid()`. If the start code is valid, this macro will evaluate to true. This library does not automatically check for valid start codes. Such error checking is left to the user to implement.\n\n## Additional Considerations\n\n### Using Flash or Disabling Cache\n\nWhen calling functions that read from or write to flash memory on the ESP32, cache is momentarily disabled and certain interrupts are prevented from firing. This can result in data corruption if the DMX driver is configured improperly.\n\nThe included `Kconfig` file in this library instructs the ESP32's build system to place the DMX driver and some of its functions into IRAM. This and other configuration options can be disabled using the ESP32's `menuconfig`. The use of `menuconfig` and `Kconfig` files is not supported when using the Arduino framework.\n\nThe DMX driver can be placed in either IRAM or flash memory. The DMX driver and its associated functions are automatically placed in IRAM to reduce the penalty associated with loading code from flash. Placing the DMX driver in flash is acceptable although less performant. When using the Arduino framework, the DMX driver may be placed in flash.\n\nWhen the driver is not placed in IRAM, functions which disable the cache will also temporarily disable the DMX driver. To prevent data corruption, it is required to gracefully disable the DMX driver before cache is disabled. This can be done with `dmx_driver_disable()`. The driver can be reenabled with `dmx_driver_enable()`. The function `dmx_driver_is_enabled()` can be used to check the status of the DMX driver.\n\n```c\n// Disable the DMX driver if it isn't already\nif (dmx_driver_is_enabled(DMX_NUM_1)) {\n  dmx_driver_disable(DMX_NUM_1);\n}\n\n// Read from or write to flash memory (or otherwise disable the cache) here...\n\ndmx_driver_enable(DMX_NUM_1);\n```\n\nDisabling and reenabling the DMX driver before disabling the cache is not required if the DMX driver is placed in IRAM.\n\n### Wiring an RS-485 Circuit\n\nDMX is transmitted over RS-485. RS-485 uses twisted-pair, half-duplex, differential signalling to ensure that data packets can be transmitted over large distances. DMX starts as a UART signal which is then driven using an RS-485 transceiver. Because the ESP32 does not have a built-in RS-485 transceiver, it is required for the ESP32 to be wired to a transceiver in most cases.\n\nRS-485 transceivers typically have four data input pins: `RO`, `DI`, `DE`, and `/RE`. `RO` is receiver output. It is the pin that the UART RX pin is connected to so that data may be read from other devices to the ESP32. `DI` is driver input. It is connected to the UART TX pin so that data may be written to other devices from the ESP32. `DE` is driver input enable. Bringing this pin high enables the input on the `DI` pin. `/RE` is receiver output enable. The overline on this pin name indicates that it is active when driven low, and inactive when driven high. Driving this pin low enables the input on the `DI` pin.\n\nBecause `DE` and `/RE` enable writing and reading respectively, and because `DE` is active high and `/RE` is active low, these pins are often shorted together. In this example, these pins are wired together and are controlled with one pin on the ESP32. This pin is called the enable pin. It can also be referred to as the RTS pin. The example schematic can be seen below.\n\n![An example RS-485 circuit](media/rs485-ckt.png)\n\nIn this example circuit, R1 and R3 are 680 ohms each. Many RS-485 breakout boards set these resistor values to 20k ohm or higher. Such high resistance values are acceptable and should still allow DMX to be written and read.\n\nR2, the 120 ohm resistor, is a terminating resistor. It is required only when using RDM. Including this resistor in schematics can also ensure system stability when connecting long lines of DMX consisting of multiple devices. If it is decided not to include this resistor, DMX-A and DMX-B should not be shorted together.\n\nMany RS-485 chips, such as the [Maxim MAX485](https://datasheets.maximintegrated.com/en/ds/MAX1487-MAX491.pdf) are 3.3v tolerant. This means that it can be controlled with the ESP32 without any additional electrical components. Other RS-485 chips may require 5v data to transmit DMX. In this case, it is required to convert the output of the ESP32 to 5v using a logic level converter.\n\n### Hardware Specifications\n\nANSI-ESTA E1.11 DMX512-A specifies that DMX devices be electrically isolated from other devices on the DMX bus. In the event of a power surge, the likely worse-case scenario would mean the failure of the RS-485 circuitry and not the entire DMX device. Some DMX devices may function without isolation, but using non-isolated equipment is not recommended.\n\n## To Do\n\nFor a list of planned features, see the [esp_dmx GitHub Projects](https://github.com/users/someweisguy/projects/5) page.\n\n## Appendix\n\n### Command Classes\n\nThe command class specifies the action of the RDM message. Responders shall always generate a response to `RDM_CC_GET_COMMAND` and `RDM_CC_SET_COMMAND` messages except when the destination UID of the message is a broadcast address. Responders shall not respond to commands sent using broadcast addressing, in order to prevent collisions.\n\n- `RDM_CC_DISC_COMMAND` The packet is an RDM discovery command.\n- `RDM_CC_DISC_COMMAND_RESPONSE` The packet is a response to an RDM discovery command.\n- `RDM_CC_GET_COMMAND` The packet is an RDM GET request.\n- `RDM_CC_GET_COMMAND_RESPONSE` The packet is a response to an RDM GET request.\n- `RDM_CC_SET_COMMAND` The packet is an RDM SET request.\n- `RDM_CC_SET_COMMAND_RESPONSE` The packet is a response to an RDM SET request.\n\n### NACK Reason Codes\n\nThe NACK reason defines the reason that the responder is unable to comply with the request.\n\n- `RDM_NR_UNKNOWN_PID` The responder cannot comply with the request because the message is not implemented in the responder.\n- `RDM_NR_FORMAT_ERROR` The responder cannot interpret the request as the controller data was not formatted correctly.\n- `RDM_NR_HARDWARE_FAULT` The responder cannot comply due to an internal hardware fault.\n- `RDM_NR_PROXY_REJECT` Proxy is not the RDM line master and cannot comply with the message.\n- `RDM_NR_WRITE_PROTECT` Set command normally allowed but being blocked currently.\n- `RDM_NR_UNSUPPORTED_COMMAND_CLASS` Not valid for command class attempted. May be used where get allowed but set is not supported.\n- `RDM_NR_DATA_OUT_OF_RANGE` Value for given parameter out of allowable range or not supported.\n- `RDM_NR_BUFFER_FULL` Buffer or queue space currently has no free space to store data.\n- `RDM_NR_PACKET_SIZE_UNSUPPORTED` Incoming message exceeds buffer capacity.\n- `RDM_NR_SUB_DEVICE_OUT_OF_RANGE` Sub-device is out of range or unknown.\n- `RDM_NR_PROXY_BUFFER_FULL` The proxy buffer is full and cannot store any more queued message or status message responses.\n\n### Parameter IDs\n\nThe table below lists the Parameter IDs specified by the RDM standard. Parameters which support GET or SET are indicated accordingly. Required parameters are automatically registered by the DMX driver if there is enough parameter space on the DMX driver. PIDs which are currently supported by this library are indicated in the \"supported\" column by the earliest version of this library which supports the PID.\n\nParameter                                   | GET | SET |Supported|Notes|\n:-------------------------------------------|:---:|:---:|:-------:|:----|\n`RDM_PID_DISC_UNIQUE_BRANCH`                | | |v3.1.0|Must be broadcast to all devices. Must be sent to the root sub-device.|\n`RDM_PID_DISC_MUTE`                         | | |v3.1.0|Must be sent to the root sub-device.|\n`RDM_PID_DISC_UN_MUTE`                      | | |v3.1.0|Must be sent to the root sub-device.|\n`RDM_PID_PROXIED_DEVICES`                   |✔️| |      |Must be sent to the root sub-device.|\n`RDM_PID_PROXIED_DEVICE_COUNT`              |✔️| |      |Must be sent to the root sub-device.|\n`RDM_PID_COMMS_STATUS`                      |✔️|✔️|      |Must be sent to the root sub-device.|\n`RDM_PID_QUEUED_MESSAGE`                    |✔️| |v4.0.0|Must be sent to the root sub-device.|\n`RDM_PID_STATUS_MESSAGE`                    |✔️| |      |Must be sent to the root sub-device.|\n`RDM_PID_STATUS_ID_DESCRIPTION`             |✔️| |      |Must be sent to the root sub-device.|\n`RDM_PID_CLEAR_STATUS_ID`                   | |✔️|      | |\n`RDM_PID_SUB_DEVICE_STATUS_REPORT_THRESHOLD`|✔️|✔️|      |Must **not** be sent to the root sub-device.|\n`RDM_PID_SUPPORTED_PARAMETERS`              |✔️| |v4.0.0|Support required only if supporting parameters beyond the minimum required set.|\n`RDM_PID_PARAMETER_DESCRIPTION`             |✔️| |v4.0.0|Must be sent to the root sub-device. Support required for manufacturer-specific PIDs exposed in `RDM_PID_SUPPORTED_PARAMETERS`.|\n`RDM_PID_DEVICE_INFO`                       |✔️| |v3.1.0| |\n`RDM_PID_PRODUCT_DETAIL_ID_LIST`            |✔️| |      | |\n`RDM_PID_DEVICE_MODEL_DESCRIPTION`          |✔️| |v4.1.0| |\n`RDM_PID_MANUFACTURER_LABEL`                |✔️| |v4.0.0| |\n`RDM_PID_DEVICE_LABEL`                      |✔️|✔️|v3.1.0| |\n`RDM_PID_FACTORY_DEFAULTS`                  |✔️|✔️|      | |\n`RDM_PID_LANGUAGE_CAPABILITIES`             |✔️| |      | |\n`RDM_PID_LANGUAGE`                          |✔️|✔️|v4.1.0| |\n`RDM_PID_SOFTWARE_VERSION_LABEL`            |✔️| |v3.1.0| |\n`RDM_PID_BOOT_SOFTWARE_VERSION_ID`          |✔️| |      | |\n`RDM_PID_BOOT_SOFTWARE_VERSION_LABEL`       |✔️| |      | |\n`RDM_PID_DMX_PERSONALITY`                   |✔️|✔️|      | |\n`RDM_PID_DMX_PERSONALITY_DESCRIPTION`       |✔️| |      | |\n`RDM_PID_DMX_START_ADDRESS`                 |✔️|✔️|v3.1.0|Support required if device uses a DMX slot.|\n`RDM_PID_SLOT_INFO`                         |✔️| |      | |\n`RDM_PID_SLOT_DESCRIPTION`                  |✔️| |      | |\n`RDM_PID_DEFAULT_SLOT_VALUE`                |✔️| |      | |\n`RDM_PID_SENSOR_DEFINITION`                 |✔️| |v4.1.0| |\n`RDM_PID_SENSOR_VALUE`                      |✔️|✔️|v4.0.0| |\n`RDM_PID_RECORD_SENSORS`                    | |✔️|v4.0.0| |\n`RDM_PID_DEVICE_HOURS`                      |✔️|✔️|v4.1.0|Some devices may not support RDM SET requests. Support for SET may be disabled using the ESP-IDF Kconfig.|\n`RDM_PID_LAMP_HOURS`                        |✔️|✔️|v4.1.0| |\n`RDM_PID_LAMP_STRIKES`                      |✔️|✔️|      | |\n`RDM_PID_LAMP_STATE`                        |✔️|✔️|      | |\n`RDM_PID_LAMP_ON_MODE`                      |✔️|✔️|      | |\n`RDM_PID_DEVICE_POWER_CYCLES`               |✔️|✔️|      | |\n`RDM_PID_DISPLAY_INVERT`                    |✔️|✔️|      | |\n`RDM_PID_DISPLAY_LEVEL`                     |✔️|✔️|      | |\n`RDM_PID_PAN_INVERT`                        |✔️|✔️|      | |\n`RDM_PID_TILT_INVERT`                       |✔️|✔️|      | |\n`RDM_PID_PAN_TILT_SWAP`                     |✔️|✔️|      | |\n`RDM_PID_REAL_TIME_CLOCK`                   |✔️|✔️|      | |\n`RDM_PID_IDENTIFY_DEVICE`                   |✔️|✔️|v3.1.0| |\n`RDM_PID_RESET_DEVICE`                      | |✔️|v4.1.0| |\n`RDM_PID_POWER_STATE`                       |✔️|✔️|      | |\n`RDM_PID_PERFORM_SELFTEST`                  |✔️|✔️|      | |\n`RDM_PID_SELF_TEST_DESCRIPTION`             |✔️| |      | |\n`RDM_PID_CAPTURE_PRESET`                    | |✔️|      | |\n`RDM_PID_PRESET_PLAYBACK`                   |✔️|✔️|      | |\n\n### Product Categories\n\nDevices shall report a product category based on the product's primary function.\n\n- `RDM_PRODUCT_CATEGORY_NOT_DECLARED` The product category is not declared.\n- `RDM_PRODUCT_CATEGORY_FIXTURE` The product is a fixture intended to create illumination.\n- `RDM_PRODUCT_CATEGORY_FIXTURE_ACCESSORY` The product is an add-on to a fixture or projector.\n- `RDM_PRODUCT_CATEGORY_PROJECTOR`The product is a light source capable of producing realistic images from another media.\n- `RDM_PRODUCT_CATEGORY_ATMOSPHERIC` The product creates atmospheric effects such as haze, fog, or pyrotechnics.\n- `RDM_PRODUCT_CATEGORY_DIMMER` The product is for intensity control, specifically dimming equipment.\n- `RDM_PRODUCT_CATEGORY_POWER` The product is for power control, other than dimming equipment.\n- `RDM_PRODUCT_CATEGORY_SCENIC` The product is a scenic device unrelated to lighting equipment.\n- `RDM_PRODUCT_CATEGORY_DATA` The product is a DMX converter, interface, or otherwise part of DMX infrastructure.\n- `RDM_PRODUCT_CATEGORY_AV` The product is audio-visual equipment.\n- `RDM_PRODUCT_CATEGORY_MONITOR` The product is monitoring equipment.\n- `RDM_PRODUCT_CATEGORY_CONTROL` The product is a controller or backup device.\n- `RDM_PRODUCT_CATEGORY_TEST` The product is test equipment.\n- `RDM_PRODUCT_CATEGORY_OTHER` The product isn't described by any of the other product categories.\n\n### Response Types\n\nResponding devices shall respond to requests only if the request was a non-broadcast request. Responding devices may respond to requests with the following response types:\n\n- `RDM_RESPONSE_TYPE_ACK` indicates that the responder has correctly received the controller message and is acting upon the request.\n- `RDM_RESPONSE_TYPE_ACK_OVERFLOW` indicates that the responder has correctly received the controller message and is acting upon the request, but there is more response data available than will fit in a single response packet. To receive the remaining information, controllers are able to send repeated requests to the same PID until the remaining information can fit in a single message.\n- `RDM_RESPONSE_TYPE_ACK_TIMER` indicates that the responder is unable to supply the requested GET information or SET confirmation within the required response time. When sending this response, responding devices include an estimated response time that must elapse before the responder can provide the required information.\n- `RDM_RESPONSE_TYPE_NACK_REASON` indicates that the responder is unable to reply with the requested GET information or unable to process the specified SET command. Responding devices must include a NACK reason code in their response. NACK reason codes are enumerated in the [appendix](#nack-reason-codes).\n- `RDM_RESPONSE_TYPE_NONE` indicates that no response was received.\n- `RDM_RESPONSE_TYPE_INVALID` indicates that a response was received, but the response was invalid. This can occur for several reasons including an invalid checksum, or an invalid packet format.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsomeweisguy%2Fesp_dmx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsomeweisguy%2Fesp_dmx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsomeweisguy%2Fesp_dmx/lists"}