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

Dmx input #109

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
97dcce0
Update esp_dmx dependency version to 3.1
arneboe Aug 14, 2023
1bc6e25
rename global dmx... variables to dmxInput...
arneboe Aug 14, 2023
5ad90e9
Adapt to new api of esp_dmx v3.1
arneboe Aug 14, 2023
a516a7b
Move dmx_input pin allocations from wled.cpp to dmx.cpp
arneboe Aug 14, 2023
b4bbf0a
Extract dmx_input from dmx.cpp into dmx_input.cpp.
arneboe Aug 14, 2023
3ee003a
Move globals to top of file and change scope to compile unit only.
arneboe Aug 14, 2023
f4c8a31
Turn dmx_into into class with state.
arneboe Aug 14, 2023
3da53bb
handle DMX_INPUT in PinManger::getOwnerText
arneboe Aug 14, 2023
369c728
handle dmx rdm identify
arneboe Aug 14, 2023
cf52c31
pin esp_dmx to a working commit.
arneboe Aug 17, 2023
fe84a9a
hack: disable dmx receiver while wifi is being activated
arneboe Aug 17, 2023
220225c
rename settings
arneboe Aug 17, 2023
7d4aeb7
add enable/disable methods for dmxInput
arneboe Aug 17, 2023
e098230
extract test for rdm identify into own method
arneboe Aug 18, 2023
fd239ac
Monitor dmx personality and dmx start address for change and update rdm
arneboe Aug 18, 2023
6262cf4
extract creation of dmx config into own method
arneboe Aug 18, 2023
11552de
handle rdm dmx address changes
arneboe Aug 18, 2023
1e9e987
comments and cleanup
arneboe Aug 18, 2023
6f761f5
track v3.1 of esp_dmx
arneboe Aug 19, 2023
6f61c8c
track rdm_device_label branch of esp_dmx until PR is done
arneboe Aug 22, 2023
03b164f
Support dmx rdm personality change
arneboe Aug 22, 2023
2465e2e
keep dmx rdm identify on if dmx disconnects.
arneboe Aug 22, 2023
6378771
Add dmx input port to configuration
arneboe Aug 23, 2023
e3f256b
Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT
arneboe Aug 23, 2023
038d41f
rename dmx.cpp -> dmx_output.cpp
arneboe Aug 23, 2023
6efd745
rename handleDMX() handleDMXOutput()
arneboe Aug 23, 2023
7cccb2e
rename initDmx() -> initDmxOutput()
arneboe Aug 23, 2023
ff2953b
Do no longer disable dmx_input when cache is disabled.
arneboe Aug 25, 2023
4a8d3e3
Move dmx_input into its own task on core 0.
arneboe Aug 25, 2023
19dfc36
npm run build
arneboe Sep 3, 2023
a204b83
make compile after rebase
arneboe Sep 3, 2023
a5820bc
npm run build after rebase
arneboe Sep 24, 2023
e727121
switch to esp_dmx v3.1 dependency
arneboe Sep 24, 2023
55c5f60
fix: pin esp_dmx version
arneboe Oct 22, 2023
d15b213
chore: adapt code style
arneboe Oct 22, 2023
f6c9708
chore: remove outdated comments
arneboe Oct 22, 2023
3b9b9f5
Revert "Rename WLED_ENABLE_DMX to WLED_ENABLE_DMX_OUTPUT"
arneboe Oct 22, 2023
42109fe
Merge branch 'mdev' into dmx_input
netmindz Jan 16, 2024
6ffbb40
HTML updates
netmindz Jan 16, 2024
bc8feed
Tweak DMX settings UI
netmindz Jan 16, 2024
74da719
Hide DMX port as just confusing to users
netmindz Jan 17, 2024
244ef2f
Update esp_dmx
netmindz Jan 17, 2024
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: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,7 @@ build_flags = ${common.build_flags} ${common_mm.build_flags_S} ;; do not inclu
;-Wsuggest-attribute=const -Wsuggest-attribute=pure ;; ask compiler for hints on attributes
-D WLED_ENABLE_DMX_INPUT
lib_deps = ${common_mm.lib_deps_S} ;; do not include ${esp32.lib_depsV4} here !!!!
https://github.com/someweisguy/esp_dmx.git#v3.0.2-beta ;; for DMX_INPUT
https://github.com/someweisguy/esp_dmx.git#47db25d ;; for DMX_INPUT
esp32_build_flags = ${esp32.build_flagsV4} ${esp32_4MB_V4_S_base.build_flags} ;; this is for esp32 only, including specific "V4" flags
esp32_lib_deps = ${esp32.lib_depsV4} ${esp32_4MB_V4_S_base.lib_deps} ;; this is for esp32 only, including specific "V4" flags
board_build.partitions = ${esp32.default_partitions}
Expand Down
12 changes: 6 additions & 6 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;

#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxTransmitPin, if_live_dmx[F("rxPin")]);
CJSON(dmxReceivePin, if_live_dmx[F("txPin")]);
CJSON(dmxEnablePin, if_live_dmx[F("enablePin")]);
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
#endif

CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
Expand Down Expand Up @@ -955,9 +955,9 @@ void serializeConfig() {
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("rxPin")] = dmxTransmitPin;
if_live_dmx[F("txPin")] = dmxReceivePin;
if_live_dmx[F("enablePin")] = dmxEnablePin;
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
#endif

if_live[F("timeout")] = realtimeTimeoutMs / 100;
Expand Down
9 changes: 5 additions & 4 deletions wled00/data/settings_sync.htm
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ <h3>Realtime</h3>
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"> <!--WLEDMM-->
<em>DMX Input Pins</em><br/>
DMX RX: <input name="DMR" type="number" min="-1" max="99"><br/>
DMX TX: <input name="DMT" type="number" min="-1" max="99"><br/>
DMX Enable: <input name="DME" type="number" min="-1" max="99"><br/>
<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/>
<div style="display: none">DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/></div>
</div>
<div id="dmxInputOff"> <!--WLEDMM-->
<br><em style="color:darkorange">This firmware build does not include DMX Input support. <br></em>
Expand Down
273 changes: 273 additions & 0 deletions wled00/dmx_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
#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) {
USER_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;
USER_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) {
USER_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;
USER_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();
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
USER_PRINTF("Error: Failed to install dmx driver\n");
return false;
}

USER_PRINTF("Listening for DMX on pin %u\n", rxPin);
USER_PRINTF("Sending DMX on pin %u\n", txPin);
USER_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)
// {
// USER_PRINTF("DMXInput: Error: Input port == output port");
// return;
// }
#endif

if (inputPortNum < 3 && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
USER_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) {
USER_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
USER_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str());
USER_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str());
USER_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) {
USER_PRINTF("Error: Failed to create dmx rcv task");
}
}
else {
USER_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) {
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 {
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
73 changes: 73 additions & 0 deletions wled00/dmx_input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#pragma once
#include <cstdint>
#include <esp_dmx.h>
#include <atomic>
#include <mutex>

/*
* Support for DMX/RDM input via serial (e.g. max485) on ESP32
* ESP32 Library from:
* https://github.com/someweisguy/esp_dmx
*/
class DMXInput
{
public:
void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum);
void update();

/**disable dmx receiver (do this before disabling the cache)*/
void disable();
void enable();

private:
/// @return true if rdm identify is active
bool isIdentifyOn() const;

/**
* Checks if the global dmx config has changed and updates the changes in rdm
*/
void checkAndUpdateConfig();

/// overrides everything and turns on all leds
void turnOnAllLeds();

/// installs the dmx driver
/// @return false on fail
bool installDriver();

/// is called by the dmx receive task regularly to receive new dmx data
void updateInternal();

// is invoked whenver the dmx start address is changed via rdm
friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);

// is invoked whenever the personality is changed via rdm
friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);

/// The internal dmx task.
/// This is the main loop of the dmx receiver. It never returns.
friend void dmxReceiverTask(void * context);

uint8_t inputPortNum = 255;
uint8_t rxPin = 255;
uint8_t txPin = 255;
uint8_t enPin = 255;

/// is written to by the dmx receive task.
byte dmxdata[DMX_PACKET_SIZE];
/// True once the dmx input has been initialized successfully
bool initialized = false; // true once init finished successfully
/// True if dmx is currently connected
std::atomic<bool> connected{false};
std::atomic<bool> identify{false};
/// Timestamp of the last time a dmx frame was received
unsigned long lastUpdate = 0;

/// Taskhandle of the dmx task that is running in the background
TaskHandle_t task;
/// Guards access to dmxData
std::mutex dmxDataLock;

};
Loading
Loading