Hostname unification

- fixes #1242
- fixes empty MQTT topic
- fixes missing hostname on change #4586
This commit is contained in:
Blaž Kristan 2025-06-11 20:58:14 +02:00 committed by Blaz Kristan
parent 66869f8341
commit e2edd38de5
6 changed files with 67 additions and 36 deletions

View File

@ -57,10 +57,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
#endif
JsonObject id = doc["id"];
getStringFromJson(cmDNS, id[F("mdns")], 33);
getStringFromJson(serverDescription, id[F("name")], 33);
getStringFromJson(cmDNS, id[F("mdns")], sizeof(cmDNS));
// fill in unique mDNS name if not set (cmDNS can be empty meaning no mDNS is used)
if (strcmp(cmDNS, DEFAULT_MDNS_NAME) == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
getStringFromJson(serverDescription, id[F("name")], sizeof(serverDescription));
if (!fromFS) {
char hostname[25];
prepareHostname(hostname, sizeof(hostname)-1);
#ifdef ARDUINO_ARCH_ESP32
WiFi.setHostname(hostname);
#else
WiFi.hostname(hostname);
#endif
}
#ifndef WLED_DISABLE_ALEXA
getStringFromJson(alexaInvocationName, id[F("inv")], 33);
getStringFromJson(alexaInvocationName, id[F("inv")], sizeof(alexaInvocationName));
#endif
CJSON(simplifiedUI, id[F("sui")]);
@ -637,8 +648,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
getStringFromJson(mqttUser, if_mqtt[F("user")], 41);
getStringFromJson(mqttPass, if_mqtt["psk"], 65); //normally not present due to security
getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41);
if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6);
getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], MQTT_MAX_TOPIC_LEN+1); // "wled/test"
if (mqttDeviceTopic[0] == 0) sprintf_P(mqttDeviceTopic, PSTR("wled/%*s"), 6, escapedMac.c_str() + 6);
getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], MQTT_MAX_TOPIC_LEN+1); // ""
CJSON(retainMqttMsg, if_mqtt[F("rtn")]);
#endif

View File

@ -144,6 +144,7 @@ Static subnet mask:<br>
function tE() {
// keep the hidden input with MAC addresses, only toggle visibility of the list UI
gId('rlc').style.display = d.Sf.RE.checked ? 'block' : 'none';
if (d.Sf.RE.checked) d.Sf.ETH.selectedIndex = 0; // disable Ethernet if ESPNOW is enabled
}
// reset remotes: initialize empty list (called from xml.cpp)
function rstR() {
@ -176,7 +177,9 @@ Static subnet mask:<br>
rC++;
gId('+').style.display = gId("rml").childElementCount < 10 ? 'inline' : 'none'; // can't append to list anymore, hide button
}
function vI(i) {
i.style.color = (i.value.match(/^[a-zA-Z0-9_\-]*$/)) ? 'white' : 'red';
}
</script>
<style>@import url("style.css");</style>
</head>
@ -187,6 +190,9 @@ Static subnet mask:<br>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button><hr>
</div>
<h2>WiFi setup</h2>
Hostname/mDNS address (empty for no mDNS):<br>
http://<input type="text" name="CM" maxlength="32" pattern="[a-zA-Z0-9_\-]*" oninput="vI(this)">.local<br>
Client IP: <span class="sip"> Not connected </span><br>
<h3>Connect to existing network</h3>
<button type="button" id="scan" onclick="N()">Scan</button><br>
<div id="wifi">
@ -198,14 +204,10 @@ Static subnet mask:<br>
</div>
DNS server address:<br>
<input name="D0" type="number" class="s" min="0" max="255" required>.<input name="D1" type="number" class="s" min="0" max="255" required>.<input name="D2" type="number" class="s" min="0" max="255" required>.<input name="D3" type="number" class="s" min="0" max="255" required><br>
<br>
mDNS address (leave empty for no mDNS):<br>
http:// <input type="text" name="CM" maxlength="32"> .local<br>
Client IP: <span class="sip"> Not connected </span> <br>
<h3>Configure Access Point</h3>
AP SSID (leave empty for no AP):<br> <input type="text" name="AS" maxlength="32"><br>
AP SSID (empty for no AP):<br> <input type="text" name="AS" maxlength="32" pattern="[a-zA-Z0-9_\-]*" oninput="vI(this)"><br>
Hide AP name: <input type="checkbox" name="AH"><br>
AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
AP password (empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
Access Point WiFi channel: <input name="AC" type="number" class="xs" min="1" max="13" required><br>
AP opens:
<select name="AB">
@ -256,7 +258,7 @@ Static subnet mask:<br>
<div id="ethd">
<h3>Ethernet Type</h3>
<select name="ETH">
<select name="ETH" onchange="if(this.selectedIndex!=0)d.Sf.RE.checked=false;">
<option value="0">None</option>
<option value="9">ABC! WLED V43 & compatible</option>
<option value="2">ESP32-POE</option>
@ -270,7 +272,8 @@ Static subnet mask:<br>
<option value="5">TwilightLord-ESP32</option>
<option value="3">WESP32</option>
<option value="1">WT32-ETH01</option>
</select><br><br>
</select><br>
<i class="warn">ESP-NOW is incompatible with Ethernet.</i>
</div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button>

View File

@ -505,7 +505,7 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val);
size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
void prepareHostname(char* hostname);
void prepareHostname(char* hostname, size_t maxLen = 32);
[[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t moduleID=255);
void releaseJSONBufferLock();

View File

@ -386,7 +386,7 @@ void WiFiEvent(WiFiEvent_t event)
case ARDUINO_EVENT_WIFI_AP_STOP:
DEBUG_PRINTLN(F("WiFi-E: AP Stopped"));
break;
#if defined(WLED_USE_ETHERNET)
#ifdef WLED_USE_ETHERNET
case ARDUINO_EVENT_ETH_START:
DEBUG_PRINTLN(F("ETH-E: Started"));
break;
@ -402,8 +402,8 @@ void WiFiEvent(WiFiEvent_t event)
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
char hostname[64];
prepareHostname(hostname);
char hostname[33];
prepareHostname(hostname, sizeof(hostname)-1);
ETH.setHostname(hostname);
showWelcomePage = false;
break;

View File

@ -63,16 +63,23 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
dnsAddress = IPAddress(request->arg(F("D0")).toInt(),request->arg(F("D1")).toInt(),request->arg(F("D2")).toInt(),request->arg(F("D3")).toInt());
}
strlcpy(cmDNS, request->arg(F("CM")).c_str(), 33);
strlcpy(cmDNS, request->arg(F("CM")).c_str(), sizeof(cmDNS));
char hostname[25];
prepareHostname(hostname, sizeof(hostname)-1);
#ifdef ARDUINO_ARCH_ESP32
WiFi.setHostname(hostname);
#else
WiFi.hostname(hostname);
#endif
apBehavior = request->arg(F("AB")).toInt();
char oldSSID[33]; strcpy(oldSSID, apSSID);
strlcpy(apSSID, request->arg(F("AS")).c_str(), 33);
strlcpy(apSSID, request->arg(F("AS")).c_str(), sizeof(apSSID));
if (!strcmp(oldSSID, apSSID) && apActive) forceReconnect = true;
apHide = request->hasArg(F("AH"));
int passlen = request->arg(F("AP")).length();
if (passlen == 0 || (passlen > 7 && !isAsterisksOnly(request->arg(F("AP")).c_str(), 65))) {
strlcpy(apPass, request->arg(F("AP")).c_str(), 65);
if (passlen == 0 || (passlen > 7 && !isAsterisksOnly(request->arg(F("AP")).c_str(), sizeof(apPass)))) {
strlcpy(apPass, request->arg(F("AP")).c_str(), sizeof(apPass));
forceReconnect = true;
}
int t = request->arg(F("AC")).toInt();
@ -368,6 +375,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (subPage == SUBPAGE_UI)
{
strlcpy(serverDescription, request->arg(F("DS")).c_str(), 33);
char hostname[25];
prepareHostname(hostname, sizeof(hostname)-1);
#ifdef ARDUINO_ARCH_ESP32
WiFi.setHostname(hostname);
#else
WiFi.hostname(hostname);
#endif
//syncToggleReceive = request->hasArg(F("ST"));
simplifiedUI = request->hasArg(F("SU"));
DEBUG_PRINTLN(F("Enumerating ledmaps"));

View File

@ -114,28 +114,30 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in
}
void prepareHostname(char* hostname)
// prepare a unique hostname based on the last 6 digits of the MAC address
// if no mDNS name or serverDescription is set, otherwise use cmDNS or serverDescription
// the hostname will be at most 24 characters long, starting with "wled-"
// and containing only alphanumeric characters and hyphens
// the hostname will not end with a hyphen and will be null-terminated
void prepareHostname(char* hostname, size_t maxLen)
{
// create a unique hostname based on the last 6 digits of the MAC address if no mDNS name or serverDescription is set
sprintf_P(hostname, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
const char *pC = serverDescription;
unsigned pos = 5; // keep "wled-"
while (*pC && pos < 24) { // while !null and not over length
const char *pC = cmDNS; // use cmDNS as hostname if set
if (strlen(pC) == 0) pC = serverDescription; // else use serverDescription
unsigned pos = 5; // keep "wled-" from unique name
while (*pC && pos < maxLen) { // while !null and not over length
if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
hostname[pos] = *pC;
pos++;
hostname[pos++] = *pC;
} else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
hostname[pos] = '-';
pos++;
hostname[pos++] = '-';
}
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
pC++;
}
//last character must not be hyphen
if (pos > 5) {
while (pos > 4 && hostname[pos -1] == '-') pos--;
// last character must not be hyphen
while (pos > 4 && hostname[pos-1] == '-') pos--;
hostname[pos] = '\0'; // terminate string (leave at least "wled")
}
}