Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Wired DMX Input support #4495

Merged
merged 35 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
702d085
rename global dmx... variables to dmxInput...
arneboe Aug 14, 2023
9e2268b
Adapt to new api of esp_dmx v3.1
arneboe Aug 14, 2023
a0ca243
Move dmx_input pin allocations from wled.cpp to dmx.cpp
arneboe Aug 14, 2023
f06a1e8
Extract dmx_input from dmx.cpp into dmx_input.cpp.
arneboe Aug 14, 2023
789d68e
Move globals to top of file and change scope to compile unit only.
arneboe Aug 14, 2023
0ad31c9
fix merge error
Jan 16, 2025
a3bcf92
Turn dmx_into into class with state.
arneboe Aug 14, 2023
5a5661f
handle dmx rdm identify
arneboe Aug 14, 2023
aed03cd
hack: disable dmx receiver while wifi is being activated
arneboe Aug 17, 2023
5525a21
rename settings
arneboe Aug 17, 2023
033c7ab
add enable/disable methods for dmxInput
arneboe Aug 17, 2023
9d8fdd0
extract test for rdm identify into own method
arneboe Aug 18, 2023
be3e331
Monitor dmx personality and dmx start address for change and update rdm
arneboe Aug 18, 2023
50b56c6
extract creation of dmx config into own method
arneboe Aug 18, 2023
2989155
handle rdm dmx address changes
arneboe Aug 18, 2023
9a3b208
comments and cleanup
arneboe Aug 18, 2023
b178c08
Support dmx rdm personality change
arneboe Aug 22, 2023
2cc5a29
keep dmx rdm identify on if dmx disconnects.
arneboe Aug 22, 2023
84eb6fd
Add dmx input port to configuration
arneboe Aug 23, 2023
7f9cc67
Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT
arneboe Aug 23, 2023
fa80c62
rename dmx.cpp -> dmx_output.cpp
arneboe Aug 23, 2023
11b48bc
rename handleDMX() handleDMXOutput()
arneboe Aug 23, 2023
67e8a00
rename initDmx() -> initDmxOutput()
arneboe Aug 23, 2023
68e9d70
Do no longer disable dmx_input when cache is disabled.
arneboe Aug 25, 2023
8f398df
Move dmx_input into its own task on core 0.
arneboe Aug 25, 2023
6598265
make compile after rebase
arneboe Sep 3, 2023
8570922
chore: adapt code style
arneboe Oct 22, 2023
d637524
chore: remove outdated comments
arneboe Oct 22, 2023
3996f02
Revert "Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT"
Jan 16, 2025
ebfc438
Tweak DMX settings UI
netmindz Jan 16, 2024
a56014b
Hide DMX port as just confusing to users
netmindz Jan 17, 2024
fc4e7a2
Swap DMX port to 1, persist user choice of port, validate port vs UAR…
netmindz Mar 7, 2024
9a6e91d
DMX Input - reinstate loggers for connection state change
netmindz Jul 30, 2024
953e994
Add DMX Input support to builds
Jan 16, 2025
a582786
Port over remaining WLEDMM part of DMX Input and adapt for AC
Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,10 @@ build_flags = -g
-DARDUINO_ARCH_ESP32 -DESP32
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-D WLED_ENABLE_DMX_INPUT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
https://github.com/someweisguy/esp_dmx.git#47db25d
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs

Expand Down
14 changes: 14 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {

tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;

#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]);
#endif

CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
Expand Down Expand Up @@ -1001,6 +1009,12 @@ void serializeConfig() {
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
if_live_dmx[F("dmxInputPort")] = dmxInputPort;
#endif

if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;
Expand Down
1 change: 1 addition & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
#define REALTIME_MODE_ARTNET 6
#define REALTIME_MODE_TPM2NET 7
#define REALTIME_MODE_DDP 8
#define REALTIME_MODE_DMX 9

//realtime override modes
#define REALTIME_OVERRIDE_NONE 0
Expand Down
13 changes: 13 additions & 0 deletions wled00/data/settings_sync.htm
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ <h3>Realtime</h3>
Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<div id="dmxInput">
<h4>Wired DMX Input Pins</h4>
DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
</div>
<div id="dmxInputOff">
<br><em style="color:darkorange">This firmware build does not include DMX Input support. <br></em>
</div>
<div id="dmxOnOff2">
<br><em style="color:darkorange">This firmware build does not include DMX output support. <br></em>
</div>
<hr class="sml">
<h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide">
Expand Down
280 changes: 280 additions & 0 deletions wled00/dmx_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
#include "wled.h"

#ifdef WLED_ENABLE_DMX_INPUT

#ifdef ESP8266
#error DMX input is only supported on ESP32
#endif

#include "dmx_input.h"
#include <rdm/responder.h>

void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);

if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb");
return;
}

if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
doSerializeConfig = true;
DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode);
}
}

void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);

if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmAddressChangedCb");
return;
}

if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
DMXAddress = std::min(512, int(addr));
doSerializeConfig = true;
DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress);
}
}

static dmx_config_t createConfig()
{
dmx_config_t config;
config.pd_size = 255;
config.dmx_start_address = DMXAddress;
config.model_id = 0;
config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE;
config.software_version_id = VERSION;
strcpy(config.device_label, "WLED_MM");

const std::string versionString = "WLED_V" + std::to_string(VERSION);
strncpy(config.software_version_label, versionString.c_str(), 32);
config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars

config.personalities[0].description = "SINGLE_RGB";
config.personalities[0].footprint = 3;
config.personalities[1].description = "SINGLE_DRGB";
config.personalities[1].footprint = 4;
config.personalities[2].description = "EFFECT";
config.personalities[2].footprint = 15;
config.personalities[3].description = "MULTIPLE_RGB";
config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
config.personalities[4].description = "MULTIPLE_DRGB";
config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
config.personalities[5].description = "MULTIPLE_RGBW";
config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
config.personalities[6].description = "EFFECT_W";
config.personalities[6].footprint = 18;
config.personalities[7].description = "EFFECT_SEGMENT";
config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
config.personalities[8].description = "EFFECT_SEGMENT_W";
config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
config.personalities[9].description = "PRESET";
config.personalities[9].footprint = 1;

config.personality_count = 10;
// rdm personalities are numbered from 1, thus we can just set the DMXMode directly.
config.current_personality = DMXMode;

return config;
}

void dmxReceiverTask(void *context)
{
DMXInput *instance = static_cast<DMXInput *>(context);
if (instance == nullptr) {
return;
}

if (instance->installDriver()) {
while (true) {
instance->updateInternal();
}
}
}

bool DMXInput::installDriver()
{

const auto config = createConfig();
DEBUG_PRINTF("DMX port: %u\n", inputPortNum);
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
DEBUG_PRINTF("Error: Failed to install dmx driver\n");
return false;
}

DEBUG_PRINTF("Listening for DMX on pin %u\n", rxPin);
DEBUG_PRINTF("Sending DMX on pin %u\n", txPin);
DEBUG_PRINTF("DMX enable pin is: %u\n", enPin);
dmx_set_pin(inputPortNum, txPin, rxPin, enPin);

rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this);
rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this);
initialized = true;
return true;
}

void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
{

#ifdef WLED_ENABLE_DMX_OUTPUT
//TODO add again once dmx output has been merged
// if(inputPortNum == dmxOutputPort)
// {
// DEBUG_PRINTF("DMXInput: Error: Input port == output port");
// return;
// }
#endif

if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum);
return;
}

if (rxPin > 0 && enPin > 0 && txPin > 0) {

const managed_pin_type pins[] = {
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
{(int8_t)rxPin, false},
{(int8_t)enPin, false}};
const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT);
if (!pinsAllocated) {
DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
DEBUG_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str());
DEBUG_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str());
DEBUG_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str());
return;
}

this->rxPin = rxPin;
this->txPin = txPin;
this->enPin = enPin;

// put dmx receiver into seperate task because it should not be blocked
// pin to core 0 because wled is running on core 1
xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0);
if (!task) {
DEBUG_PRINTF("Error: Failed to create dmx rcv task");
}
}
else {
DEBUG_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set");
return;
}
}

void DMXInput::updateInternal()
{
if (!initialized) {
return;
}

checkAndUpdateConfig();

dmx_packet_t packet;
unsigned long now = millis();
if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) {
if (!packet.err) {
if(!connected) {
DEBUG_PRINTLN("DMX Input - connected");
}
connected = true;
identify = isIdentifyOn();
if (!packet.is_rdm) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
dmx_read(inputPortNum, dmxdata, packet.size);
}
}
else {
connected = false;
}
}
else {
if(connected) {
DEBUG_PRINTLN("DMX Input - disconnected");
}
connected = false;
}
}


void DMXInput::update()
{
if (identify) {
turnOnAllLeds();
}
else if (connected) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0);
}
}

void DMXInput::turnOnAllLeds()
{
// TODO not sure if this is the correct way?
const uint16_t numPixels = strip.getLengthTotal();
for (uint16_t i = 0; i < numPixels; ++i)
{
strip.setPixelColor(i, 255, 255, 255, 255);
}
strip.setBrightness(255, true);
strip.show();
}

void DMXInput::disable()
{
if (initialized) {
dmx_driver_disable(inputPortNum);
}
}
void DMXInput::enable()
{
if (initialized) {
dmx_driver_enable(inputPortNum);
}
}

bool DMXInput::isIdentifyOn() const
{

uint8_t identify = 0;
const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify);
// gotIdentify should never be false because it is a default parameter in rdm
// but just in case we check for it anyway
return bool(identify) && gotIdentify;
}

void DMXInput::checkAndUpdateConfig()
{

/**
* The global configuration variables are modified by the web interface.
* If they differ from the driver configuration, we have to update the driver
* configuration.
*/

const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum);
if (currentPersonality != DMXMode) {
DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode);
dmx_set_current_personality(inputPortNum, DMXMode);
}

const uint16_t currentAddr = dmx_get_start_address(inputPortNum);
if (currentAddr != DMXAddress) {
DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress);
dmx_set_start_address(inputPortNum, DMXAddress);
}
}

#endif
Loading
Loading