Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 28 additions & 1 deletion wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <Arduino.h>
#include <IPAddress.h>
#ifdef ARDUINO_ARCH_ESP32
#include <ESPmDNS.h>
#include "src/dependencies/network/Network.h" // for isConnected() (& WiFi)
#include "driver/ledc.h"
#include "soc/ledc_struct.h"
#if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
Expand All @@ -25,6 +27,7 @@
#include "bus_wrapper.h"
#include <bits/unique_ptr.h>

extern char cmDNS[];
extern bool cctICused;
extern bool useParallelI2S;

Expand Down Expand Up @@ -95,7 +98,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format
}

//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
Expand Down Expand Up @@ -692,6 +695,10 @@ BusNetwork::BusNetwork(const BusConfig &bc)
_hasCCT = false;
_UDPchannels = _hasWhite + 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
#ifdef ARDUINO_ARCH_ESP32
_hostname = bc.text;
resolveHostname(); // resolve hostname to IP address if needed
#endif
_data = (uint8_t*)d_calloc(_len, _UDPchannels);
_valid = (_data != nullptr);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
Expand Down Expand Up @@ -726,6 +733,19 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
return 4;
}

#ifdef ARDUINO_ARCH_ESP32
void BusNetwork::resolveHostname() {
static unsigned long nextResolve = 0;
if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
nextResolve = millis() + 600000; // resolve only every 10 minutes
IPAddress clnt;
if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);
else WiFi.hostByName(_hostname.c_str(), clnt);
if (clnt != IPAddress()) _client = clnt;
}
}
Comment on lines +737 to +746
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement error handling for hostname resolution failures.

The resolveHostname() method has good throttling (10-minute intervals) but could benefit from improved error handling and logging.

Consider adding error handling and debug logging:

 void BusNetwork::resolveHostname() {
   static unsigned long nextResolve = 0;
   if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
     nextResolve = millis() + 600000; // resolve only every 10 minutes
     IPAddress clnt;
+    bool resolved = false;
     if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);
+    if (clnt != IPAddress()) resolved = true;
     else WiFi.hostByName(_hostname.c_str(), clnt);
+    if (!resolved && clnt != IPAddress()) resolved = true;
-    if (clnt != IPAddress()) _client = clnt;
+    if (resolved) {
+      _client = clnt;
+      DEBUGBUS_PRINTF_P(PSTR("Bus: Resolved hostname %s to %s\n"), _hostname.c_str(), _client.toString().c_str());
+    } else {
+      DEBUGBUS_PRINTF_P(PSTR("Bus: Failed to resolve hostname %s\n"), _hostname.c_str());
+    }
   }
 }
🤖 Prompt for AI Agents
In wled00/bus_manager.cpp around lines 737 to 746, the resolveHostname() method
lacks error handling and logging for hostname resolution failures. Add checks to
detect if MDNS.queryHost or WiFi.hostByName fail to resolve the hostname, and
log appropriate error or debug messages indicating the failure. This will
improve observability and help diagnose network issues during hostname
resolution.

#endif

// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
std::vector<LEDType> BusNetwork::getLEDTypes() {
return {
Expand Down Expand Up @@ -911,6 +931,13 @@ void BusManager::on() {
}
}
}
#else
for (auto &bus : busses) if (bus->isVirtual()) {
// virtual/network bus should check for IP change if hostname is specified
// otherwise there are no endpoints to force DNS resolution
BusNetwork &b = static_cast<BusNetwork&>(*bus);
b.resolveHostname();
}
#endif
#ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle();
Expand Down
14 changes: 12 additions & 2 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class Bus {
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual size_t getBusSize() const { return sizeof(Bus); }
virtual const String getCustomText() const { return String(); }
Copy link
Member

@softhack007 softhack007 Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two ideas for improvement:

  • Return a const string & (aka reference) to avoid string duplication in heap
  • return text, instead of an empty string. Or add a comment to explain why this must return an empty string.


inline bool hasRGB() const { return _hasRgb; }
inline bool hasWhite() const { return _hasWhite; }
Expand Down Expand Up @@ -215,7 +216,7 @@ class Bus {
uint8_t _autoWhiteMode;
// global Auto White Calculation override
static uint8_t _gAWM;
// _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()):
// _cct has the following meanings (see calculateCCT() & BusManager::setSegmentCCT()):
// -1 means to extract approximate CCT value in K from RGB (in calcualteCCT())
// [0,255] is the exact CCT value where 0 means warm and 255 cold
// [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin())
Expand Down Expand Up @@ -342,6 +343,10 @@ class BusNetwork : public Bus {
size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override;
void cleanup();
#ifdef ARDUINO_ARCH_ESP32
void resolveHostname();
const String getCustomText() const override { return _hostname; }
#endif

static std::vector<LEDType> getLEDTypes();

Expand All @@ -351,6 +356,9 @@ class BusNetwork : public Bus {
uint8_t _UDPchannels;
bool _broadcastLock;
uint8_t *_data;
#ifdef ARDUINO_ARCH_ESP32
String _hostname;
#endif
};


Expand All @@ -368,8 +376,9 @@ struct BusConfig {
uint16_t frequency;
uint8_t milliAmpsPerLed;
uint16_t milliAmpsMax;
String text;

BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some text? I thought this is about an mDNS name.
Would it be possible to find a more meaningful name for this parameter?

: count(std::max(len,(uint16_t)1))
, start(pstart)
, colorOrder(pcolorOrder)
Expand All @@ -379,6 +388,7 @@ struct BusConfig {
, frequency(clock_kHz)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
, text(sometext)
{
refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
Expand Down
8 changes: 5 additions & 3 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh

busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax);
String host = elm[F("text")] | String();
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host);
doInitBusses = true; // finalization done in beginStrip()
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
}
Expand Down Expand Up @@ -379,7 +380,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s);
btnPin[s] = -1;
PinManager::deallocatePin(pin,PinOwner::Button);
}
}
//if touch pin, enable the touch interrupt on ESP32 S2 & S3
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
else
Expand Down Expand Up @@ -976,6 +977,7 @@ void serializeConfig(JsonObject root) {
ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
ins[F("text")] = bus->getCustomText();
}

JsonArray hw_com = hw.createNestedArray(F("com"));
Expand Down Expand Up @@ -1340,4 +1342,4 @@ void serializeConfigSec() {
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
}
}
16 changes: 12 additions & 4 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
function isC3() { return maxA == 6 && maxD == 2; } // NOTE: see const.h
function isS2() { return maxA == 8 && maxD == 12 && maxV == 4; } // NOTE: see const.h
function isS3() { return maxA == 8 && maxD == 12 && maxV == 6; } // NOTE: see const.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very fragile. Especially if someone changes the number of virtual buses by overriding the value in const.h, then "isS3" will stop working.

function pinsOK() {
var ok = true;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
Expand Down Expand Up @@ -271,7 +276,7 @@
gRGBW |= hasW(t); // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
Expand All @@ -281,6 +286,8 @@
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266
});
// display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none';
Expand Down Expand Up @@ -463,6 +470,7 @@
<span id="p2d${s}"></span><input type="number" name="L2${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${s}"></span><input type="number" name="L3${s}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${s}"></span><input type="number" name="L4${s}" class="s" onchange="UI();pinUpd(this);"/>
<div id="net${s}h" class="hide">Host: <input type="text" name="HS${s}" maxlength="32" pattern="[a-zA-Z0-9_\\-]*" onchange="UI()"/>.local</div>
<div id="dig${s}r" style="display:inline"><br><span id="rev${s}">Reversed</span>: <input type="checkbox" name="CV${s}"></div>
<div id="dig${s}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${s}f" style="display:inline"><br><span id="off${s}">Off Refresh</span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
Expand All @@ -479,7 +487,7 @@
if (type.t != undefined && type.t != "") {
opt.setAttribute('data-type', type.t);
}
sel.appendChild(opt);
sel.appendChild(opt);
}
}
});
Expand Down Expand Up @@ -586,7 +594,7 @@
var cs = false;
for (var i=1; i < gEBCN("iST").length; i++) {
var s = chrID(i);
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value);
if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;}
}
Expand Down Expand Up @@ -617,7 +625,7 @@

function receivedText(e) {
let lines = e.target.result;
let c = JSON.parse(lines);
let c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
// remove all existing outputs
Expand Down
5 changes: 4 additions & 1 deletion wled00/data/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ input {
input:disabled {
color: #888;
}
input:invalid {
color: #f00;
}
input[type="text"],
input[type="number"],
input[type="password"],
Expand Down Expand Up @@ -202,4 +205,4 @@ td {
#btns select {
width: 144px;
}
}
}
5 changes: 4 additions & 1 deletion wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
unsigned length, start, maMax;
uint8_t pins[5] = {255, 255, 255, 255, 255};
String text;

// this will set global ABL max current used when per-port ABL is not used
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
Expand Down Expand Up @@ -174,6 +175,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
break;
Expand Down Expand Up @@ -224,9 +226,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
}
type |= request->hasArg(rf) << 7; // off refresh override
text = request->arg(hs).substring(0,31);
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax);
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
Expand Down
2 changes: 2 additions & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
settingsScript.print(F("addLEDs(1);"));
uint8_t pins[5];
int nPins = bus->getPins(pins);
Expand Down Expand Up @@ -350,6 +351,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,sp,speed);
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
printSetFormValue(settingsScript,hs,bus->getCustomText().c_str());
sumMa += bus->getMaxCurrent();
}
printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa);
Expand Down