Philips hue robustness improvements

* reduce JSON buffer from 1024 to 640
* ESP32: define buffer globally (reduces likelyhood of crashes due to low stack)
* added some sanity checks after string ops
* delete hueClient object after closing the connection

* make functions static if not used outside of hue.cpp, re-shuffle order where needed
* rename JSON buffer object: root -> jsonHUEroot
This commit is contained in:
Frank
2026-01-28 22:12:24 +01:00
parent de0f5c89fc
commit 4b47714f68
2 changed files with 54 additions and 40 deletions

View File

@@ -6,6 +6,13 @@
#ifndef WLED_DISABLE_HUESYNC #ifndef WLED_DISABLE_HUESYNC
// WLEDMM moved out of wled.h
static AsyncClient* hueClient = nullptr;
#ifdef ARDUINO_ARCH_ESP32
StaticJsonDocument<640> jsonHUEroot; // (was 1024) - 640 bytes should be more than enough
#endif
void handleHue() void handleHue()
{ {
if (hueReceived) if (hueReceived)
@@ -27,37 +34,18 @@ void handleHue()
reconnectHue(); reconnectHue();
} else { } else {
hueClient->close(); hueClient->close();
delete hueClient; hueClient = nullptr;
if (hueError == HUE_ERROR_ACTIVE) hueError = HUE_ERROR_INACTIVE; if (hueError == HUE_ERROR_ACTIVE) hueError = HUE_ERROR_INACTIVE;
} }
} }
void reconnectHue() static void onHueError(void* arg, AsyncClient* client, int8_t error)
{ {
if (!WLED_CONNECTED || !huePollingEnabled) return; USER_PRINTLN("Hue err " + String(error));
DEBUG_PRINTLN(F("Hue reconnect"));
if (hueClient == nullptr) {
hueClient = new AsyncClient();
hueClient->onConnect(&onHueConnect, hueClient);
hueClient->onData(&onHueData, hueClient);
hueClient->onError(&onHueError, hueClient);
hueAuthRequired = (strlen(hueApiKey)<20);
}
hueClient->connect(hueIP, 80);
}
void onHueError(void* arg, AsyncClient* client, int8_t error)
{
DEBUG_PRINTLN(F("Hue err"));
hueError = HUE_ERROR_TIMEOUT; hueError = HUE_ERROR_TIMEOUT;
} }
void onHueConnect(void* arg, AsyncClient* client) static void sendHuePoll()
{
DEBUG_PRINTLN(F("Hue connect"));
sendHuePoll();
}
void sendHuePoll()
{ {
if (hueClient == nullptr || !hueClient->connected()) return; if (hueClient == nullptr || !hueClient->connected()) return;
String req = ""; String req = "";
@@ -81,7 +69,13 @@ void sendHuePoll()
hueLastRequestSent = millis(); hueLastRequestSent = millis();
} }
void onHueData(void* arg, AsyncClient* client, void *data, size_t len) static void onHueConnect(void* arg, AsyncClient* client)
{
DEBUG_PRINTLN(F("Hue connect"));
sendHuePoll();
}
static void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
{ {
if (!len) return; if (!len) return;
char* str = (char*)data; char* str = (char*)data;
@@ -89,19 +83,22 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
DEBUG_PRINTLN(str); DEBUG_PRINTLN(str);
//only get response body //only get response body
str = strstr(str,"\r\n\r\n"); str = strstr(str,"\r\n\r\n");
if (str == nullptr) return; if ((str == nullptr) || strlen(str) < 4) return;
str += 4; str += 4;
StaticJsonDocument<1024> root; #if !defined(ARDUINO_ARCH_ESP32)
StaticJsonDocument<640> jsonHUEroot; // was 1024 - completely ovrersized.
#endif
if (str[0] == '[') //is JSON array if (str[0] == '[') //is JSON array
{ {
auto error = deserializeJson(root, str); auto error = deserializeJson(jsonHUEroot, str);
if (error) if (error)
{ {
USER_PRINTF("Hue error: failed to parse (len =%d) \t: %s\n", strlen(str), str);
hueError = HUE_ERROR_JSON_PARSING; return; hueError = HUE_ERROR_JSON_PARSING; return;
} }
int hueErrorCode = root[0][F("error")]["type"]; int hueErrorCode = jsonHUEroot[0][F("error")]["type"];
if (hueErrorCode)//hue bridge returned error if (hueErrorCode)//hue bridge returned error
{ {
hueError = hueErrorCode; hueError = hueErrorCode;
@@ -116,7 +113,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
if (hueAuthRequired) if (hueAuthRequired)
{ {
const char* apikey = root[0][F("success")][F("username")]; const char* apikey = jsonHUEroot[0][F("success")][F("username")];
if (apikey != nullptr && strlen(apikey) < sizeof(hueApiKey)) if (apikey != nullptr && strlen(apikey) < sizeof(hueApiKey))
{ {
strlcpy(hueApiKey, apikey, sizeof(hueApiKey)); strlcpy(hueApiKey, apikey, sizeof(hueApiKey));
@@ -131,10 +128,12 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
str = strstr(str,"state"); str = strstr(str,"state");
if (str == nullptr) return; if (str == nullptr) return;
str = strstr(str,"{"); str = strstr(str,"{");
if (str == nullptr) return;
auto error = deserializeJson(root, str); auto error = deserializeJson(jsonHUEroot, str);
if (error) if (error)
{ {
USER_PRINTF("Hue error: failed to parse (len =%d) \t: %s\n", strlen(str), str);
hueError = HUE_ERROR_JSON_PARSING; return; hueError = HUE_ERROR_JSON_PARSING; return;
} }
@@ -142,27 +141,27 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
uint16_t hueHue=0, hueCt=0; uint16_t hueHue=0, hueCt=0;
byte hueBri=0, hueSat=0, hueColormode=0; byte hueBri=0, hueSat=0, hueColormode=0;
if (root["on"]) { if (jsonHUEroot["on"]) {
if (root.containsKey("bri")) //Dimmable device if (jsonHUEroot.containsKey("bri")) //Dimmable device
{ {
hueBri = root["bri"]; hueBri = jsonHUEroot["bri"];
hueBri++; hueBri++;
const char* cm =root[F("colormode")]; const char* cm =jsonHUEroot[F("colormode")];
if (cm != nullptr) //Color device if (cm != nullptr) //Color device
{ {
if (strstr(cm,("ct")) != nullptr) //ct mode if (strstr(cm,("ct")) != nullptr) //ct mode
{ {
hueCt = root["ct"]; hueCt = jsonHUEroot["ct"];
hueColormode = 3; hueColormode = 3;
} else if (strstr(cm,"xy") != nullptr) //xy mode } else if (strstr(cm,"xy") != nullptr) //xy mode
{ {
hueX = root["xy"][0]; // 0.5051 hueX = jsonHUEroot["xy"][0]; // 0.5051
hueY = root["xy"][1]; // 0.4151 hueY = jsonHUEroot["xy"][1]; // 0.4151
hueColormode = 1; hueColormode = 1;
} else //hs mode } else //hs mode
{ {
hueHue = root["hue"]; hueHue = jsonHUEroot["hue"];
hueSat = root[F("sat")]; hueSat = jsonHUEroot[F("sat")];
hueColormode = 2; hueColormode = 2;
} }
} }
@@ -202,6 +201,21 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
} }
hueReceived = true; hueReceived = true;
} }
void reconnectHue() // this is also called from async_tcp task handleSettingsSet()
{
if (!WLED_CONNECTED || !huePollingEnabled) return;
DEBUG_PRINTLN(F("Hue reconnect"));
if (hueClient == nullptr) {
hueClient = new AsyncClient();
hueClient->onConnect(&onHueConnect, hueClient);
hueClient->onData(&onHueData, hueClient);
hueClient->onError(&onHueError, hueClient);
hueAuthRequired = (strlen(hueApiKey)<20);
}
hueClient->connect(hueIP, 80);
}
#else #else
void handleHue(){} void handleHue(){}
void reconnectHue(){} void reconnectHue(){}

View File

@@ -761,7 +761,7 @@ WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS
WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws"))); WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws")));
#endif #endif
WLED_GLOBAL AsyncClient *hueClient _INIT(NULL); //WLED_GLOBAL AsyncClient *hueClient _INIT(NULL); // WLEDMM moved into hue.cpp
WLED_GLOBAL AsyncWebHandler *editHandler _INIT(nullptr); WLED_GLOBAL AsyncWebHandler *editHandler _INIT(nullptr);
// udp interface objects // udp interface objects