2020-03-25 09:00:55 +01:00
|
|
|
#include "wled.h"
|
2020-03-31 02:38:08 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Sync to Philips hue lights
|
|
|
|
*/
|
2020-03-25 09:00:55 +01:00
|
|
|
|
2018-11-01 15:36:13 +01:00
|
|
|
#ifndef WLED_DISABLE_HUESYNC
|
2020-03-25 09:00:55 +01:00
|
|
|
|
2018-02-28 00:27:10 +01:00
|
|
|
void handleHue()
|
|
|
|
{
|
2019-02-18 22:34:21 +01:00
|
|
|
if (hueReceived)
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-02-22 16:17:32 +01:00
|
|
|
colorUpdated(NOTIFIER_CALL_MODE_HUE); hueReceived = false;
|
2019-02-18 22:34:21 +01:00
|
|
|
if (hueStoreAllowed && hueNewKey)
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-11-06 22:12:48 +01:00
|
|
|
serializeConfigSec(); //save api key
|
2019-02-18 22:34:21 +01:00
|
|
|
hueStoreAllowed = false;
|
|
|
|
hueNewKey = false;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
}
|
2019-10-18 14:06:07 +02:00
|
|
|
|
|
|
|
if (!WLED_CONNECTED || hueClient == nullptr || millis() - hueLastRequestSent < huePollIntervalMs) return;
|
|
|
|
|
|
|
|
hueLastRequestSent = millis();
|
|
|
|
if (huePollingEnabled)
|
|
|
|
{
|
|
|
|
reconnectHue();
|
|
|
|
} else {
|
|
|
|
hueClient->close();
|
2020-02-25 02:19:12 +01:00
|
|
|
if (hueError == HUE_ERROR_ACTIVE) hueError = HUE_ERROR_INACTIVE;
|
2019-10-18 14:06:07 +02:00
|
|
|
}
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
|
2019-02-18 22:34:21 +01:00
|
|
|
void reconnectHue()
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-10-18 13:26:39 +02:00
|
|
|
if (!WLED_CONNECTED || !huePollingEnabled) return;
|
2020-09-20 01:18:31 +02:00
|
|
|
DEBUG_PRINTLN(F("Hue reconnect"));
|
2019-02-18 22:34:21 +01:00
|
|
|
if (hueClient == nullptr) {
|
|
|
|
hueClient = new AsyncClient();
|
|
|
|
hueClient->onConnect(&onHueConnect, hueClient);
|
|
|
|
hueClient->onData(&onHueData, hueClient);
|
|
|
|
hueClient->onError(&onHueError, hueClient);
|
|
|
|
hueAuthRequired = (strlen(hueApiKey)<20);
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
2019-02-18 22:34:21 +01:00
|
|
|
hueClient->connect(hueIP, 80);
|
|
|
|
}
|
|
|
|
|
|
|
|
void onHueError(void* arg, AsyncClient* client, int8_t error)
|
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
DEBUG_PRINTLN(F("Hue err"));
|
2020-02-25 02:19:12 +01:00
|
|
|
hueError = HUE_ERROR_TIMEOUT;
|
2019-02-18 22:34:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void onHueConnect(void* arg, AsyncClient* client)
|
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
DEBUG_PRINTLN(F("Hue connect"));
|
2019-02-18 22:34:21 +01:00
|
|
|
sendHuePoll();
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendHuePoll()
|
|
|
|
{
|
|
|
|
if (hueClient == nullptr || !hueClient->connected()) return;
|
|
|
|
String req = "";
|
|
|
|
if (hueAuthRequired)
|
|
|
|
{
|
2020-02-25 02:19:12 +01:00
|
|
|
req += F("POST /api HTTP/1.1\r\nHost: ");
|
2019-02-18 22:34:21 +01:00
|
|
|
req += hueIP.toString();
|
2020-02-25 02:19:12 +01:00
|
|
|
req += F("\r\nContent-Length: 25\r\n\r\n{\"devicetype\":\"wled#esp\"}");
|
2019-02-18 22:34:21 +01:00
|
|
|
} else
|
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
req += F("GET /api/");
|
2019-02-18 22:34:21 +01:00
|
|
|
req += hueApiKey;
|
2020-09-20 01:18:31 +02:00
|
|
|
req += F("/lights/");
|
|
|
|
req += String(huePollLightId);
|
2020-02-25 02:19:12 +01:00
|
|
|
req += F(" HTTP/1.1\r\nHost: ");
|
2019-02-18 22:34:21 +01:00
|
|
|
req += hueIP.toString();
|
|
|
|
req += "\r\n\r\n";
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
2019-02-18 22:34:21 +01:00
|
|
|
hueClient->add(req.c_str(), req.length());
|
|
|
|
hueClient->send();
|
2018-02-28 00:27:10 +01:00
|
|
|
hueLastRequestSent = millis();
|
|
|
|
}
|
|
|
|
|
2019-03-03 23:27:52 +01:00
|
|
|
void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-03-03 23:27:52 +01:00
|
|
|
if (!len) return;
|
|
|
|
char* str = (char*)data;
|
2018-02-28 00:27:10 +01:00
|
|
|
DEBUG_PRINTLN(hueApiKey);
|
2019-03-03 23:27:52 +01:00
|
|
|
DEBUG_PRINTLN(str);
|
|
|
|
//only get response body
|
|
|
|
str = strstr(str,"\r\n\r\n");
|
|
|
|
if (str == nullptr) return;
|
|
|
|
str += 4;
|
|
|
|
|
2019-06-21 23:12:58 +02:00
|
|
|
StaticJsonDocument<512> root;
|
2019-03-03 23:27:52 +01:00
|
|
|
if (str[0] == '[') //is JSON array
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-06-21 23:12:58 +02:00
|
|
|
auto error = deserializeJson(root, str);
|
|
|
|
if (error)
|
2019-03-03 23:27:52 +01:00
|
|
|
{
|
2020-02-25 02:19:12 +01:00
|
|
|
hueError = HUE_ERROR_JSON_PARSING; return;
|
2019-03-03 23:27:52 +01:00
|
|
|
}
|
2019-06-21 23:12:58 +02:00
|
|
|
|
2020-09-20 01:18:31 +02:00
|
|
|
int hueErrorCode = root[0][F("error")][F("type")];
|
2019-03-03 23:27:52 +01:00
|
|
|
if (hueErrorCode)//hue bridge returned error
|
|
|
|
{
|
2020-02-25 02:19:12 +01:00
|
|
|
hueError = hueErrorCode;
|
2019-03-03 23:27:52 +01:00
|
|
|
switch (hueErrorCode)
|
|
|
|
{
|
2020-02-25 02:19:12 +01:00
|
|
|
case 1: hueAuthRequired = true; break; //Unauthorized user
|
|
|
|
case 3: huePollingEnabled = false; break; //Invalid light ID
|
|
|
|
case 101: hueAuthRequired = true; break; //link button not presset
|
2019-03-03 23:27:52 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hueAuthRequired)
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
const char* apikey = root[0][F("success")][F("username")];
|
2019-03-05 10:59:15 +01:00
|
|
|
if (apikey != nullptr && strlen(apikey) < sizeof(hueApiKey))
|
2019-03-03 23:27:52 +01:00
|
|
|
{
|
2019-03-05 10:59:15 +01:00
|
|
|
strcpy(hueApiKey, apikey);
|
2019-03-03 23:27:52 +01:00
|
|
|
hueAuthRequired = false;
|
|
|
|
hueNewKey = true;
|
|
|
|
}
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
2019-02-18 22:34:21 +01:00
|
|
|
return;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
2019-02-18 22:34:21 +01:00
|
|
|
|
2019-03-03 23:27:52 +01:00
|
|
|
//else, assume it is JSON object, look for state and only parse that
|
|
|
|
str = strstr(str,"state");
|
|
|
|
if (str == nullptr) return;
|
|
|
|
str = strstr(str,"{");
|
|
|
|
|
2019-06-21 23:12:58 +02:00
|
|
|
auto error = deserializeJson(root, str);
|
|
|
|
if (error)
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-02-25 02:19:12 +01:00
|
|
|
hueError = HUE_ERROR_JSON_PARSING; return;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
float hueX=0, hueY=0;
|
|
|
|
uint16_t hueHue=0, hueCt=0;
|
2018-03-14 13:16:28 +01:00
|
|
|
byte hueBri=0, hueSat=0, hueColormode=0;
|
2019-03-03 23:27:52 +01:00
|
|
|
|
|
|
|
if (root["on"]) {
|
|
|
|
if (root.containsKey("bri")) //Dimmable device
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-03-03 23:27:52 +01:00
|
|
|
hueBri = root["bri"];
|
|
|
|
hueBri++;
|
2020-09-20 01:18:31 +02:00
|
|
|
const char* cm =root[F("colormode")];
|
2019-03-03 23:27:52 +01:00
|
|
|
if (cm != nullptr) //Color device
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
if (strstr(cm,("ct")) != nullptr) //ct mode
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-03-03 23:27:52 +01:00
|
|
|
hueCt = root["ct"];
|
|
|
|
hueColormode = 3;
|
|
|
|
} else if (strstr(cm,"xy") != nullptr) //xy mode
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2019-03-03 23:27:52 +01:00
|
|
|
hueX = root["xy"][0]; // 0.5051
|
|
|
|
hueY = root["xy"][1]; // 0.4151
|
|
|
|
hueColormode = 1;
|
|
|
|
} else //hs mode
|
2018-02-28 00:27:10 +01:00
|
|
|
{
|
2020-09-20 01:18:31 +02:00
|
|
|
hueHue = root[F("hue")];
|
|
|
|
hueSat = root[F("sat")];
|
2019-03-03 23:27:52 +01:00
|
|
|
hueColormode = 2;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else //On/Off device
|
|
|
|
{
|
2018-03-14 13:16:28 +01:00
|
|
|
hueBri = briLast;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
hueBri = 0;
|
|
|
|
}
|
2019-03-03 23:27:52 +01:00
|
|
|
|
2020-02-25 02:19:12 +01:00
|
|
|
hueError = HUE_ERROR_ACTIVE;
|
2019-03-03 23:27:52 +01:00
|
|
|
|
|
|
|
//apply vals
|
2018-02-28 00:27:10 +01:00
|
|
|
if (hueBri != hueBriLast)
|
|
|
|
{
|
|
|
|
if (hueApplyOnOff)
|
|
|
|
{
|
|
|
|
if (hueBri==0) {bri = 0;}
|
2018-03-14 13:16:28 +01:00
|
|
|
else if (bri==0 && hueBri>0) bri = briLast;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
|
|
|
if (hueApplyBri)
|
|
|
|
{
|
|
|
|
if (hueBri>0) bri = hueBri;
|
|
|
|
}
|
|
|
|
hueBriLast = hueBri;
|
|
|
|
}
|
|
|
|
if (hueApplyColor)
|
|
|
|
{
|
|
|
|
switch(hueColormode)
|
|
|
|
{
|
|
|
|
case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,col); hueXLast = hueX; hueYLast = hueY; break;
|
|
|
|
case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,col); hueHueLast = hueHue; hueSatLast = hueSat; break;
|
|
|
|
case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,col); hueCtLast = hueCt; break;
|
|
|
|
}
|
|
|
|
}
|
2019-02-18 22:34:21 +01:00
|
|
|
hueReceived = true;
|
2018-02-28 00:27:10 +01:00
|
|
|
}
|
2018-11-01 15:36:13 +01:00
|
|
|
#else
|
|
|
|
void handleHue(){}
|
2019-03-07 16:35:02 +01:00
|
|
|
void reconnectHue(){}
|
2018-11-01 15:36:13 +01:00
|
|
|
#endif
|