Added Philips Hue synchronization
Added HU and SA API calls to set color via Hue and Saturation values
This commit is contained in:
parent
1b0d735e50
commit
e7e11b8bd2
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -196,27 +196,25 @@ Receive <input type="checkbox" name="NRCBR">Brightness, <input type="checkbox" n
|
||||
Send notifications on direct change: <input type="checkbox" name="NSDIR"> <br>
|
||||
Send notifications on button press: <input type="checkbox" name="NSBTN"> <br>
|
||||
Send Alexa notifications: <input type="checkbox" name="NSALX"> <br>
|
||||
<!--Send Philips Hue change notifications: <input type="checkbox" name="NSHUE">-->
|
||||
Send Philips Hue change notifications: <input type="checkbox" name="NSHUE">
|
||||
<h3>Alexa Voice Assistant</h3>
|
||||
Emulate Alexa device: <input type="checkbox" name="ALEXA"> <br>
|
||||
Alexa invocation name: <input name="AINVN" maxlength="32"><br>
|
||||
<h3>Philips Hue</h3>
|
||||
Coming soon! Not yet implemented!
|
||||
<!--<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>
|
||||
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>
|
||||
Hue Bridge IP:<br>
|
||||
<input name="HUIP0" type="number" min="0" max="255" required> .
|
||||
<input name="HUIP1" type="number" min="0" max="255" required> .
|
||||
<input name="HUIP2" type="number" min="0" max="255" required> .
|
||||
<input name="HUIP3" type="number" min="0" max="255" required> <br>
|
||||
<b>For successful pairing, press the pushlink button on the bridge, then save this page!</b><br>
|
||||
<b>Press the pushlink button on the bridge, after that save this page!</b><br>
|
||||
(when first connecting)<br>
|
||||
<i> Use 0 for group and light to turn off sending/receiving </i><br>
|
||||
Update Hue group <input name="HUEGR" type="number" min="0" max="99" required> <br>
|
||||
Send <input type="checkbox" name="HUEIO"> On/Off, <input type="checkbox" name="HUEBR"> Brightness, and <input type="checkbox" name="HUECL"> Color<br>
|
||||
Poll Hue light <input name="HUELI" type="number" min="0" max="99" required> every <input name="HUEPL" type="number" min="100" max="62000" required> ms<br>
|
||||
<!--Update Hue group <input name="HUEGR" type="number" min="0" max="99" required> <br>
|
||||
Send <input type="checkbox" name="HUEIO"> On/Off, <input type="checkbox" name="HUEBR"> Brightness, and <input type="checkbox" name="HUECL"> Color<br>-->
|
||||
Poll Hue light <input name="HUELI" type="number" min="1" max="99" required> every <input name="HUEPI" type="number" min="100" max="65000" required> ms: <input type="checkbox" name="HUEPL"><br>
|
||||
Then, receive <input type="checkbox" name="HURIO"> On/Off, <input type="checkbox" name="HURBR"> Brightness, and <input type="checkbox" name="HURCL"> Color<br>
|
||||
After device color update, ignore Hue updates for <input name="HUELI" type="number" min="0" max="255" required> minutes<br>
|
||||
Hue status: <span class="hms"> Internal ESP error! </span>-->
|
||||
<!--After device color update, ignore Hue updates for <input name="HUELI" type="number" min="0" max="255" required> minutes<br>-->
|
||||
Hue status: <span class="hms"> Internal ESP Error! </span>
|
||||
<hr>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit" name="SUBM">Save</button>
|
||||
</form>
|
||||
@ -333,7 +331,7 @@ HTTP traffic is not encrypted. An attacker in the same network could intercept f
|
||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||
Enable ArduinoOTA: <input type="checkbox" name="AROTA"><br>
|
||||
<h3>About</h3>
|
||||
<a href="https://github.com/Aircoookie/WLED">WLED</a> version 0.5.0<br>
|
||||
<a href="https://github.com/Aircoookie/WLED">WLED</a> version 0.5.1<br>
|
||||
(c) 2016-2018 Christian Schwinne <br>
|
||||
<i>Licensed under the MIT license</i><br><br>
|
||||
<i>Uses libraries:</i><br>
|
||||
|
@ -3,7 +3,7 @@
|
||||
*/
|
||||
/*
|
||||
* @title WLED project sketch
|
||||
* @version 0.5.0
|
||||
* @version 0.5.1
|
||||
* @author Christian Schwinne
|
||||
*/
|
||||
|
||||
@ -12,10 +12,12 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "src/dependencies/webserver/WebServer.h"
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#endif
|
||||
#include <EEPROM.h>
|
||||
#include <ArduinoOTA.h>
|
||||
@ -30,8 +32,8 @@
|
||||
#include "WS2812FX.h"
|
||||
|
||||
//version in format yymmddb (b = daily build)
|
||||
#define VERSION 1802251
|
||||
const String versionString = "0.5.0";
|
||||
#define VERSION 1802273
|
||||
const String versionString = "0.5.1";
|
||||
|
||||
//AP and OTA default passwords (change them!)
|
||||
String appass = "wled1234";
|
||||
@ -152,6 +154,22 @@ unsigned long countdownTime = 1514764800L;
|
||||
|
||||
double transitionResolution = 0.011;
|
||||
|
||||
//hue
|
||||
long hueLastRequestSent = 0;
|
||||
bool huePollingEnabled = false, hueAttempt = false;
|
||||
uint16_t huePollIntervalMs = 2500;
|
||||
uint32_t huePollIntervalMsTemp = 2500;
|
||||
String hueApiKey = "api";
|
||||
uint8_t huePollLightId = 1;
|
||||
IPAddress hueIP = (0,0,0,0);
|
||||
bool notifyHue = true;
|
||||
bool hueApplyOnOff = true, hueApplyBri = true, hueApplyColor = true;
|
||||
String hueError = "Inactive";
|
||||
uint16_t hueFailCount = 0;
|
||||
float hueXLast=0, hueYLast=0;
|
||||
uint16_t hueHueLast=0, hueCtLast=0;
|
||||
uint8_t hueSatLast=0, hueBriLast=0;
|
||||
|
||||
//Internal vars
|
||||
byte col[]{0, 0, 0};
|
||||
byte col_old[]{0, 0, 0};
|
||||
@ -241,20 +259,13 @@ WebServer server(80);
|
||||
#else
|
||||
ESP8266WebServer server(80);
|
||||
#endif
|
||||
HTTPClient hueClient;
|
||||
ESP8266HTTPUpdateServer httpUpdater;
|
||||
WiFiUDP notifierUdp;
|
||||
WiFiUDP ntpUdp;
|
||||
|
||||
WS2812FX strip = WS2812FX(LEDCOUNT);
|
||||
|
||||
//eeprom Version code, enables default settings instead of 0 init on update
|
||||
#define EEPVER 4
|
||||
//0 -> old version, default
|
||||
//1 -> 0.4p 1711272 and up
|
||||
//2 -> 0.4p 1711302 and up
|
||||
//3 -> 0.4 1712121 and up
|
||||
//4 -> 0.5.0 and up
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_PRINT(x) Serial.print (x)
|
||||
#define DEBUG_PRINTLN(x) Serial.println (x)
|
||||
@ -333,6 +344,7 @@ void loop() {
|
||||
handleAlexa();
|
||||
if (!arlsTimeout) //block stuff if WARLS is enabled
|
||||
{
|
||||
handleHue();
|
||||
handleNightlight();
|
||||
#ifdef USEOVERLAYS
|
||||
handleOverlays();
|
||||
|
@ -1,8 +1,18 @@
|
||||
/*
|
||||
* Methods to handle saving and loading to non-volatile memory
|
||||
* EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map
|
||||
*/
|
||||
|
||||
#define EEPSIZE 2048
|
||||
#define EEPSIZE 3072
|
||||
|
||||
//eeprom Version code, enables default settings instead of 0 init on update
|
||||
#define EEPVER 5
|
||||
//0 -> old version, default
|
||||
//1 -> 0.4p 1711272 and up
|
||||
//2 -> 0.4p 1711302 and up
|
||||
//3 -> 0.4 1712121 and up
|
||||
//4 -> 0.5.0 and up
|
||||
//5 -> 0.5.1 and up
|
||||
|
||||
void clearEEPROM()
|
||||
{
|
||||
@ -144,6 +154,24 @@ void saveSettingsToEEPROM()
|
||||
EEPROM.write(i, cssFont.charAt(i-950));
|
||||
}
|
||||
|
||||
EEPROM.write(2048, huePollingEnabled);
|
||||
//EEPROM.write(2049, hueUpdatingEnabled);
|
||||
for (int i = 2050; i < 2054; ++i)
|
||||
{
|
||||
EEPROM.write(i, hueIP[i-2050]);
|
||||
}
|
||||
for (int i = 2054; i < 2100; ++i)
|
||||
{
|
||||
EEPROM.write(i, hueApiKey.charAt(i-2054));
|
||||
}
|
||||
EEPROM.write(2100, (huePollIntervalMs >> 0) & 0xFF);
|
||||
EEPROM.write(2101, (huePollIntervalMs >> 8) & 0xFF);
|
||||
EEPROM.write(2102, notifyHue);
|
||||
EEPROM.write(2103, hueApplyOnOff);
|
||||
EEPROM.write(2104, hueApplyBri);
|
||||
EEPROM.write(2105, hueApplyColor);
|
||||
EEPROM.write(2106, huePollLightId);
|
||||
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
@ -297,6 +325,26 @@ void loadSettingsFromEEPROM(bool first)
|
||||
receiveNotificationEffects = receiveNotificationBrightness;
|
||||
}
|
||||
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
|
||||
if (lastEEPROMversion > 4) {
|
||||
huePollingEnabled = EEPROM.read(2048);
|
||||
//hueUpdatingEnabled = EEPROM.read(2049);
|
||||
for (int i = 2050; i < 2054; ++i)
|
||||
{
|
||||
hueIP[i-2050] = EEPROM.read(i);
|
||||
}
|
||||
hueApiKey = "";
|
||||
for (int i = 2054; i < 2100; ++i)
|
||||
{
|
||||
if (EEPROM.read(i) == 0) break;
|
||||
hueApiKey += char(EEPROM.read(i));
|
||||
}
|
||||
huePollIntervalMs = ((EEPROM.read(2100) << 0) & 0xFF) + ((EEPROM.read(2101) << 8) & 0xFF00);
|
||||
notifyHue = EEPROM.read(2102);
|
||||
hueApplyOnOff = EEPROM.read(2103);
|
||||
hueApplyBri = EEPROM.read(2104);
|
||||
hueApplyColor = EEPROM.read(2105);
|
||||
huePollLightId = EEPROM.read(2106);
|
||||
}
|
||||
|
||||
bootPreset = EEPROM.read(389);
|
||||
wifiLock = EEPROM.read(393);
|
||||
@ -316,6 +364,9 @@ void loadSettingsFromEEPROM(bool first)
|
||||
//custom macro memory (16 slots/ each 64byte)
|
||||
//1024-2047 reserved
|
||||
|
||||
//user MOD memory
|
||||
//2944 - 3071 reserved
|
||||
|
||||
useHSB = useHSBDefault;
|
||||
|
||||
strip.setMode(effectCurrent);
|
||||
|
@ -187,9 +187,21 @@ String getSettings(uint8_t subPage)
|
||||
resp += ds + "NRCFX" + c + receiveNotificationEffects +";";
|
||||
resp += ds + "NSDIR" + c + notifyDirectDefault +";";
|
||||
resp += ds + "NSBTN" + c + notifyButton +";";
|
||||
resp += ds + "NSHUE" + c + notifyHue +";";
|
||||
resp += ds + "ALEXA" + c + alexaEnabled +";";
|
||||
resp += ds + "AINVN" + v + "\"" + alexaInvocationName + "\";";
|
||||
resp += ds + "NSALX" + c + alexaNotify +";";
|
||||
resp += ds + "HUIP0" + v + hueIP[0] +";";
|
||||
resp += ds + "HUIP1" + v + hueIP[1] +";";
|
||||
resp += ds + "HUIP2" + v + hueIP[2] +";";
|
||||
resp += ds + "HUIP3" + v + hueIP[3] +";";
|
||||
resp += ds + "HUELI" + v + huePollLightId +";";
|
||||
resp += ds + "HUEPI" + v + huePollIntervalMs +";";
|
||||
resp += ds + "HUEPL" + c + huePollingEnabled +";";
|
||||
resp += ds + "HURIO" + c + hueApplyOnOff +";";
|
||||
resp += ds + "HURBR" + c + hueApplyBri +";";
|
||||
resp += ds + "HURCL" + c + hueApplyColor +";";
|
||||
resp += dg + "(\"hms\")[0]" + ih + "\"" + hueError + "\";";
|
||||
}
|
||||
|
||||
if (subPage == 5)
|
||||
|
@ -281,6 +281,37 @@ void handleSettingsSet(uint8_t subPage)
|
||||
alexaEnabled = server.hasArg("ALEXA");
|
||||
if (server.hasArg("AINVN")) alexaInvocationName = server.arg("AINVN");
|
||||
alexaNotify = server.hasArg("NSALX");
|
||||
notifyHue = server.hasArg("NSHUE");
|
||||
for (int i=0;i<4;i++){
|
||||
String a = "HUIP"+String(i);
|
||||
if (server.hasArg(a))
|
||||
{
|
||||
int j = server.arg(a).toInt();
|
||||
if (j >= 0 && j <= 255) hueIP[i] = j;
|
||||
}
|
||||
}
|
||||
if (server.hasArg("HUELI"))
|
||||
{
|
||||
int i = server.arg("HUELI").toInt();
|
||||
if (i > 0) huePollLightId = i;
|
||||
}
|
||||
if (server.hasArg("HUEPI"))
|
||||
{
|
||||
int i = server.arg("HUEPI").toInt();
|
||||
if (i > 50) huePollIntervalMs = i;
|
||||
}
|
||||
hueApplyOnOff = server.hasArg("HURIO");
|
||||
hueApplyBri = server.hasArg("HURBR");
|
||||
hueApplyColor = server.hasArg("HURCL");
|
||||
if (server.hasArg("HUEPL"))
|
||||
{
|
||||
if (!huePollingEnabled) hueAttempt = true;
|
||||
if (!setupHue()) hueAttempt = true;
|
||||
} else
|
||||
{
|
||||
huePollingEnabled = false;
|
||||
hueError = "Inactive";
|
||||
}
|
||||
}
|
||||
|
||||
//TIME
|
||||
@ -358,6 +389,19 @@ boolean handleSet(String req)
|
||||
if (pos > 0) {
|
||||
bri = req.substring(pos + 3).toInt();
|
||||
}
|
||||
|
||||
//set hue
|
||||
pos = req.indexOf("HU=");
|
||||
if (pos > 0) {
|
||||
uint16_t temphue = req.substring(pos + 3).toInt();
|
||||
uint8_t tempsat = 255;
|
||||
pos = req.indexOf("SA=");
|
||||
if (pos > 0) {
|
||||
tempsat = req.substring(pos + 3).toInt();
|
||||
}
|
||||
colorHStoRGB(temphue,tempsat,(req.indexOf("H2")>0)? col_sec:col);
|
||||
}
|
||||
|
||||
//set red value
|
||||
pos = req.indexOf("&R=");
|
||||
if (pos > 0) {
|
||||
@ -481,6 +525,19 @@ boolean handleSet(String req)
|
||||
}
|
||||
}
|
||||
|
||||
//set hue polling light: 0 -off
|
||||
pos = req.indexOf("HP=");
|
||||
if (pos > 0) {
|
||||
int id = req.substring(pos + 3).toInt();
|
||||
if (id > 0)
|
||||
{
|
||||
if (id < 100) huePollLightId = id;
|
||||
setupHue();
|
||||
} else {
|
||||
huePollingEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
//set default control mode (0 - RGB, 1 - HSB)
|
||||
pos = req.indexOf("MD=");
|
||||
if (pos > 0) {
|
||||
|
@ -56,6 +56,13 @@ void wledInit()
|
||||
DEBUG_PRINT("Connected! IP address: ");
|
||||
DEBUG_PRINTLN(WiFi.localIP());
|
||||
|
||||
if (hueIP[0] == 0)
|
||||
{
|
||||
hueIP[0] = WiFi.localIP()[0];
|
||||
hueIP[1] = WiFi.localIP()[1];
|
||||
hueIP[2] = WiFi.localIP()[2];
|
||||
}
|
||||
|
||||
// Set up mDNS responder:
|
||||
if (cmdns != NULL && !onlyAP && !MDNS.begin(cmdns.c_str())) {
|
||||
DEBUG_PRINTLN("Error setting up MDNS responder!");
|
||||
@ -147,7 +154,13 @@ void wledInit()
|
||||
|
||||
server.on("/settings/sync", HTTP_POST, [](){
|
||||
handleSettingsSet(4);
|
||||
if (hueAttempt)
|
||||
{
|
||||
serveMessage(200,"Hue setup result",hueError,253);
|
||||
} else {
|
||||
serveMessage(200,"Sync settings saved.","Redirecting...",1);
|
||||
}
|
||||
hueAttempt = false;
|
||||
});
|
||||
|
||||
server.on("/settings/time", HTTP_POST, [](){
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* This file allows you to add own functionality to WLED more easily
|
||||
*
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
* EEPROM bytes 2944 to 3071 are reserved for your custom use case.
|
||||
*/
|
||||
|
||||
void userBeginPreConnection()
|
||||
|
@ -13,6 +13,7 @@ void notify(uint8_t callMode)
|
||||
case 2: if (!notifyButton) return; break;
|
||||
case 4: if (!notifyDirect) return; break;
|
||||
case 6: if (!notifyDirect) return; break; //fx change
|
||||
case 7: if (!notifyHue) return; break;
|
||||
default: return;
|
||||
}
|
||||
byte udpOut[WLEDPACKETSIZE];
|
||||
|
@ -51,7 +51,7 @@ bool colorChanged()
|
||||
|
||||
void colorUpdated(int callMode)
|
||||
{
|
||||
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (no not.) (NN)6: fx changed
|
||||
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (NN)6: fx changed 7: hue
|
||||
if (!colorChanged())
|
||||
{
|
||||
if (callMode == 6) notify(6);
|
||||
|
@ -3,25 +3,118 @@
|
||||
*/
|
||||
void colorCTtoRGB(uint16_t mired, uint8_t* rgb) //white spectrum to rgb
|
||||
{
|
||||
|
||||
//this is only an approximation using WS2812B with gamma correction enabled
|
||||
if (mired > 475)
|
||||
{
|
||||
rgb[0]=255;rgb[1]=199;rgb[2]=92;//500
|
||||
} else if (mired > 425)
|
||||
{
|
||||
rgb[0]=255;rgb[1]=213;rgb[2]=118;//450
|
||||
} else if (mired > 375)
|
||||
{
|
||||
rgb[0]=255;rgb[1]=216;rgb[2]=118;//400
|
||||
} else if (mired > 325)
|
||||
{
|
||||
rgb[0]=255;rgb[1]=234;rgb[2]=140;//350
|
||||
} else if (mired > 275)
|
||||
{
|
||||
rgb[0]=255;rgb[1]=243;rgb[2]=160;//300
|
||||
} else if (mired > 225)
|
||||
{
|
||||
rgb[0]=250;rgb[1]=255;rgb[2]=188;//250
|
||||
} else if (mired > 175)
|
||||
{
|
||||
rgb[0]=247;rgb[1]=255;rgb[2]=215;//200
|
||||
} else
|
||||
{
|
||||
rgb[0]=237;rgb[1]=255;rgb[2]=239;//150
|
||||
}
|
||||
}
|
||||
|
||||
void colorHSBtoRGB(uint16_t hue, uint8_t sat, uint8_t bri, uint8_t* rgb) //hue, sat, bri to rgb
|
||||
void colorHStoRGB(uint16_t hue, uint8_t sat, uint8_t* rgb) //hue, sat to rgb
|
||||
{
|
||||
|
||||
float h = ((float)hue)/65535.0;
|
||||
float s = ((float)sat)/255.0;
|
||||
uint8_t i = floor(h*6);
|
||||
float f = h * 6-i;
|
||||
float p = 255 * (1-s);
|
||||
float q = 255 * (1-f*s);
|
||||
float t = 255 * (1-(1-f)*s);
|
||||
switch (i%6) {
|
||||
case 0: rgb[0]=255,rgb[1]=t,rgb[2]=p;break;
|
||||
case 1: rgb[0]=q,rgb[1]=255,rgb[2]=p;break;
|
||||
case 2: rgb[0]=p,rgb[1]=255,rgb[2]=t;break;
|
||||
case 3: rgb[0]=p,rgb[1]=q,rgb[2]=255;break;
|
||||
case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break;
|
||||
case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q;
|
||||
}
|
||||
}
|
||||
|
||||
void colorXYtoRGB(float x, float y, uint8_t* rgb) //coordinates to rgb (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
|
||||
{
|
||||
float z = 1.0f - x - y;
|
||||
//float Y = 1.0f; // Brightness, we handle this separately
|
||||
float X = (1.0f / y) * x;
|
||||
float Z = (1.0f / y) * z;
|
||||
rgb[0] = (int)(X * 1.656492f - 0.354851f - Z * 0.255038f);
|
||||
rgb[1] = (int)(-X * 0.707196f + 1.655397f + Z * 0.036152f);
|
||||
rgb[2] = (int)(X * 0.051713f - 0.121364f + Z * 1.011530f);
|
||||
float r = (int)255*(X * 1.656492f - 0.354851f - Z * 0.255038f);
|
||||
float g = (int)255*(-X * 0.707196f + 1.655397f + Z * 0.036152f);
|
||||
float b = (int)255*(X * 0.051713f - 0.121364f + Z * 1.011530f);
|
||||
if (r > b && r > g && r > 1.0f) {
|
||||
// red is too big
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0f;
|
||||
} else if (g > b && g > r && g > 1.0f) {
|
||||
// green is too big
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0f;
|
||||
} else if (b > r && b > g && b > 1.0f) {
|
||||
// blue is too big
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0f;
|
||||
}
|
||||
// Apply gamma correction
|
||||
r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
|
||||
g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
|
||||
b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
|
||||
|
||||
if (r > b && r > g) {
|
||||
// red is biggest
|
||||
if (r > 1.0f) {
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0f;
|
||||
}
|
||||
}
|
||||
else if (g > b && g > r) {
|
||||
// green is biggest
|
||||
if (g > 1.0f) {
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0f;
|
||||
}
|
||||
}
|
||||
else if (b > r && b > g) {
|
||||
// blue is biggest
|
||||
if (b > 1.0f) {
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0f;
|
||||
}
|
||||
}
|
||||
rgb[0] = 255.0*r;
|
||||
rgb[1] = 255.0*g;
|
||||
rgb[2] = 255.0*b;
|
||||
}
|
||||
|
||||
void colorRGBtoXY(uint8_t* rgb, float* xy){} //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
|
||||
void colorRGBtoXY(uint8_t* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
|
||||
{
|
||||
float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f;
|
||||
float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f;
|
||||
float Z = rgb[0] * 0.000088f + rgb[1] * 0.072310f + rgb[2] * 0.986039f;
|
||||
xy[0] = X / (X + Y + Z);
|
||||
xy[1] = Y / (X + Y + Z);
|
||||
}
|
||||
|
||||
void colorRGBtoRGBW(uint8_t* rgb, uint8_t* rgbw){} //rgb to rgbw, not imlemented yet
|
||||
|
@ -1,4 +1,207 @@
|
||||
/*
|
||||
* Sync to Philips hue lights
|
||||
*/
|
||||
void foo(){}
|
||||
|
||||
void handleHue()
|
||||
{
|
||||
if (huePollingEnabled && WiFi.status() == WL_CONNECTED)
|
||||
{
|
||||
if (millis() - hueLastRequestSent > huePollIntervalMsTemp)
|
||||
{
|
||||
sendHuePoll(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool setupHue()
|
||||
{
|
||||
if (WiFi.status() == WL_CONNECTED) //setup needed
|
||||
{
|
||||
if (hueApiKey.length()>20) //api key is probably ok
|
||||
{
|
||||
if (sendHuePoll(false))
|
||||
{
|
||||
huePollingEnabled = true;
|
||||
return true;
|
||||
}
|
||||
if (hueError.charAt(0) == 'R' || hueError.charAt(0) == 'I') return false; //can't connect
|
||||
delay(20);
|
||||
}
|
||||
sendHuePoll(true); //new API key
|
||||
if (hueError.charAt(0) != 'C') return false; //still some error
|
||||
delay(20);
|
||||
if (sendHuePoll(false))
|
||||
{
|
||||
huePollingEnabled = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sendHuePoll(bool sAuth)
|
||||
{
|
||||
bool st;
|
||||
hueClient.setReuse(true);
|
||||
hueClient.setTimeout(250);
|
||||
String hueURL = "http://";
|
||||
hueURL += hueIP.toString();
|
||||
hueURL += "/api/";
|
||||
if (!sAuth) {
|
||||
hueURL += hueApiKey;
|
||||
hueURL += "/lights/" + String(huePollLightId);
|
||||
}
|
||||
hueClient.begin(hueURL);
|
||||
int httpCode = (sAuth)? hueClient.POST("{\"devicetype\":\"wled#esp\"}"):hueClient.GET();
|
||||
//TODO this request may block operation for ages
|
||||
|
||||
if (httpCode>0){
|
||||
st = handleHueResponse(hueClient.getString(),sAuth);
|
||||
} else {
|
||||
hueError = "Request timed out";
|
||||
st = false;
|
||||
}
|
||||
if (!st){ //error
|
||||
if (hueFailCount<7) huePollIntervalMsTemp*=2; // only poll every 5min when unable to connect
|
||||
hueFailCount++;
|
||||
if (hueFailCount > 150) huePollingEnabled = false; //disable after many hours offline
|
||||
}
|
||||
hueLastRequestSent = millis();
|
||||
return st;
|
||||
}
|
||||
|
||||
bool handleHueResponse(String hueResp, bool isAuth)
|
||||
{
|
||||
DEBUG_PRINTLN(hueApiKey);
|
||||
DEBUG_PRINTLN(hueResp);
|
||||
if (hueResp.indexOf("error")>0)//hue bridge returned error
|
||||
{
|
||||
int hueErrorCode = getJsonValue(&hueResp,"type").toInt();
|
||||
switch (hueErrorCode)
|
||||
{
|
||||
case 1: hueError = "Unauthorized"; break;
|
||||
case 3: hueError = "Invalid light ID"; break;
|
||||
case 101: hueError = "Link button not pressed"; break;
|
||||
default: hueError = "Bridge Error " + String(hueErrorCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAuth)
|
||||
{
|
||||
String tempApi = getJsonValue(&hueResp,"username");
|
||||
if (tempApi.length()>0)
|
||||
{
|
||||
hueApiKey = tempApi;
|
||||
return true;
|
||||
}
|
||||
hueError = "Invalid response";
|
||||
return false;
|
||||
}
|
||||
|
||||
float hueX=0, hueY=0;
|
||||
uint16_t hueHue=0, hueCt=0;
|
||||
uint8_t hueBri=0, hueSat=0, hueColormode=0;
|
||||
|
||||
if (getJsonValue(&hueResp,"on").charAt(0) == 't')
|
||||
{
|
||||
String tempV = getJsonValue(&hueResp,"bri");
|
||||
if (tempV.length()>0) //Dimmable device
|
||||
{
|
||||
hueBri = (tempV.toInt())+1;
|
||||
tempV = getJsonValue(&hueResp,"colormode");
|
||||
if (hueApplyColor && tempV.length()>0) //Color device
|
||||
{
|
||||
if (tempV.charAt(0) == 'x') //xy mode
|
||||
{
|
||||
tempV = getJsonValue(&hueResp,"xy");
|
||||
if (tempV.length()>0) //valid
|
||||
{
|
||||
hueColormode = 1;
|
||||
hueX = tempV.toFloat();
|
||||
tempV = tempV.substring(tempV.indexOf(',')+1);
|
||||
hueY = tempV.toFloat();
|
||||
}
|
||||
} else if (tempV.charAt(0) == 'h') //hs mode
|
||||
{
|
||||
tempV = getJsonValue(&hueResp,"hue");
|
||||
if (tempV.length()>0) //valid
|
||||
{
|
||||
hueColormode = 2;
|
||||
hueHue = tempV.toInt();
|
||||
tempV = getJsonValue(&hueResp,"sat");
|
||||
if (tempV.length()>0) //valid
|
||||
{
|
||||
hueSat = tempV.toInt();
|
||||
}
|
||||
}
|
||||
} else //ct mode
|
||||
{
|
||||
tempV = getJsonValue(&hueResp,"\"ct"); //dirty hack to not get effect value instead
|
||||
if (tempV.length()>0) //valid
|
||||
{
|
||||
hueColormode = 3;
|
||||
hueCt = tempV.toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else //On/Off device
|
||||
{
|
||||
hueBri = bri_last;
|
||||
}
|
||||
} else
|
||||
{
|
||||
hueBri = 0;
|
||||
}
|
||||
hueFailCount = 0;
|
||||
huePollIntervalMsTemp = huePollIntervalMs;
|
||||
hueError = "Connected";
|
||||
//applying vals
|
||||
if (hueBri != hueBriLast)
|
||||
{
|
||||
bri = hueBri;
|
||||
if (hueApplyOnOff)
|
||||
{
|
||||
if (hueBri==0) {bri = 0;}
|
||||
else if (bri==0 && hueBri>0) bri = bri_last;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
colorUpdated(7);
|
||||
return true;
|
||||
}
|
||||
|
||||
String getJsonValue(String* req, String key)
|
||||
{
|
||||
//TODO may replace with ArduinoJSON if too complex
|
||||
//this is horribly inefficient and designed to work only in this case
|
||||
uint16_t pos = req->indexOf(key);
|
||||
String b = req->substring(pos + key.length()+2);
|
||||
if (b.charAt(0)=='\"') //is string
|
||||
{
|
||||
return b.substring(1,b.substring(1).indexOf('\"')+1);
|
||||
} else if (b.charAt(0)=='[') //is array
|
||||
{
|
||||
return b.substring(1,b.indexOf(']'));
|
||||
} else //is primitive type
|
||||
{
|
||||
return b.substring(0,b.indexOf(',')); //this works only if value not last
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user