2017-01-28 13:41:01 +00:00
/*
2019-05-20 14:09:42 +01:00
xdrv_20_hue . ino - Philips Hue support for Sonoff - Tasmota
2018-11-06 16:33:51 +00:00
2019-01-01 12:55:01 +00:00
Copyright ( C ) 2019 Heiko Krupp and Theo Arends
2018-11-06 16:33:51 +00:00
2017-05-13 12:02:10 +01:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
2018-11-06 16:33:51 +00:00
2017-05-13 12:02:10 +01:00
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
2018-11-06 16:33:51 +00:00
2017-05-13 12:02:10 +01:00
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2017-01-28 13:41:01 +00:00
*/
2019-06-16 15:43:23 +01:00
# if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
2017-01-28 13:41:01 +00:00
/*********************************************************************************************\
2019-05-20 14:09:42 +01:00
* Philips Hue bridge emulation
*
2017-01-28 13:41:01 +00:00
* Hue Bridge UPNP support routines
* Need to send 3 response packets with varying ST and USN
2017-09-02 13:37:02 +01:00
*
2017-07-15 14:07:30 +01:00
* Using Espressif Inc Mac Address of 5 C : CF : 7F : 00 : 00 : 00
* Philips Lighting is 00 : 17 : 88 : 00 : 00 : 00
2017-01-28 13:41:01 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2017-07-15 14:07:30 +01:00
2019-05-20 14:09:42 +01:00
# define XDRV_20 20
2017-01-28 13:41:01 +00:00
const char HUE_RESPONSE [ ] PROGMEM =
2017-07-15 14:07:30 +01:00
" HTTP/1.1 200 OK \r \n "
2017-01-28 13:41:01 +00:00
" HOST: 239.255.255.250:1900 \r \n "
" CACHE-CONTROL: max-age=100 \r \n "
" EXT: \r \n "
2019-03-27 17:09:27 +00:00
" LOCATION: http://%s:80/description.xml \r \n "
2017-09-09 14:36:30 +01:00
" SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.17.0 \r \n "
2019-03-27 17:09:27 +00:00
" hue-bridgeid: %s \r \n " ;
2017-09-02 13:37:02 +01:00
const char HUE_ST1 [ ] PROGMEM =
2017-07-15 14:07:30 +01:00
" ST: upnp:rootdevice \r \n "
2019-03-27 17:09:27 +00:00
" USN: uuid:%s::upnp:rootdevice \r \n "
2017-01-28 13:41:01 +00:00
" \r \n " ;
const char HUE_ST2 [ ] PROGMEM =
2019-03-27 17:09:27 +00:00
" ST: uuid:%s \r \n "
" USN: uuid:%s \r \n "
2017-01-28 13:41:01 +00:00
" \r \n " ;
const char HUE_ST3 [ ] PROGMEM =
2017-09-09 14:36:30 +01:00
" ST: urn:schemas-upnp-org:device:basic:1 \r \n "
2019-03-27 17:09:27 +00:00
" USN: uuid:%s \r \n "
2017-07-15 14:07:30 +01:00
" \r \n " ;
2017-09-02 13:37:02 +01:00
2018-11-14 13:32:09 +00:00
String HueBridgeId ( void )
2017-01-28 13:41:01 +00:00
{
2017-07-15 14:07:30 +01:00
String temp = WiFi . macAddress ( ) ;
temp . replace ( " : " , " " ) ;
String bridgeid = temp . substring ( 0 , 6 ) + " FFFE " + temp . substring ( 6 ) ;
return bridgeid ; // 5CCF7FFFFE139F3D
}
2018-11-14 13:32:09 +00:00
String HueSerialnumber ( void )
2017-07-15 14:07:30 +01:00
{
String serial = WiFi . macAddress ( ) ;
serial . replace ( " : " , " " ) ;
serial . toLowerCase ( ) ;
return serial ; // 5ccf7f139f3d
2017-01-28 13:41:01 +00:00
}
2018-11-14 13:32:09 +00:00
String HueUuid ( void )
2017-01-28 13:41:01 +00:00
{
2017-07-15 14:07:30 +01:00
String uuid = F ( " f6543a06-da50-11ba-8d8f- " ) ;
2017-10-18 17:22:34 +01:00
uuid + = HueSerialnumber ( ) ;
2017-07-15 14:07:30 +01:00
return uuid ; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
2017-01-28 13:41:01 +00:00
}
2018-11-14 13:32:09 +00:00
void HueRespondToMSearch ( void )
2017-01-28 13:41:01 +00:00
{
2017-04-25 17:24:42 +01:00
char message [ TOPSZ ] ;
2017-01-28 13:41:01 +00:00
2018-01-05 11:26:19 +00:00
TickerMSearch . detach ( ) ;
if ( PortUdp . beginPacket ( udp_remote_ip , udp_remote_port ) ) {
2019-03-27 17:09:27 +00:00
char response [ 320 ] ;
snprintf_P ( response , sizeof ( response ) , HUE_RESPONSE , WiFi . localIP ( ) . toString ( ) . c_str ( ) , HueBridgeId ( ) . c_str ( ) ) ;
int len = strlen ( response ) ;
snprintf_P ( response + len , sizeof ( response ) - len , HUE_ST1 , HueUuid ( ) . c_str ( ) ) ;
PortUdp . write ( response ) ;
2017-10-18 17:22:34 +01:00
PortUdp . endPacket ( ) ;
2017-07-15 14:07:30 +01:00
2019-03-27 17:09:27 +00:00
snprintf_P ( response + len , sizeof ( response ) - len , HUE_ST2 , HueUuid ( ) . c_str ( ) , HueUuid ( ) . c_str ( ) ) ;
PortUdp . write ( response ) ;
2017-10-18 17:22:34 +01:00
PortUdp . endPacket ( ) ;
2017-07-15 14:07:30 +01:00
2019-03-27 17:09:27 +00:00
snprintf_P ( response + len , sizeof ( response ) - len , HUE_ST3 , HueUuid ( ) . c_str ( ) ) ;
PortUdp . write ( response ) ;
2017-10-18 17:22:34 +01:00
PortUdp . endPacket ( ) ;
2017-01-28 13:41:01 +00:00
2017-09-02 13:37:02 +01:00
snprintf_P ( message , sizeof ( message ) , PSTR ( D_3_RESPONSE_PACKETS_SENT ) ) ;
2017-01-28 13:41:01 +00:00
} else {
2017-09-02 13:37:02 +01:00
snprintf_P ( message , sizeof ( message ) , PSTR ( D_FAILED_TO_SEND_RESPONSE ) ) ;
2017-01-28 13:41:01 +00:00
}
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_UPNP D_HUE " %s " D_TO " %s:%d " ) ,
2018-01-05 11:26:19 +00:00
message , udp_remote_ip . toString ( ) . c_str ( ) , udp_remote_port ) ;
udp_response_mutex = false ;
2017-01-28 13:41:01 +00:00
}
2017-07-15 14:07:30 +01:00
/*********************************************************************************************\
* Hue web server additions
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2017-02-21 17:14:33 +00:00
const char HUE_DESCRIPTION_XML [ ] PROGMEM =
" <?xml version= \" 1.0 \" ?> "
" <root xmlns= \" urn:schemas-upnp-org:device-1-0 \" > "
" <specVersion> "
2017-07-15 14:07:30 +01:00
" <major>1</major> "
" <minor>0</minor> "
2017-02-21 17:14:33 +00:00
" </specVersion> "
2017-10-25 13:27:30 +01:00
// "<URLBase>http://{x1/</URLBase>"
" <URLBase>http://{x1:80/</URLBase> "
2017-02-21 17:14:33 +00:00
" <device> "
2017-07-15 14:07:30 +01:00
" <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType> "
2017-10-25 13:27:30 +01:00
" <friendlyName>Amazon-Echo-HA-Bridge ({x1)</friendlyName> "
// "<friendlyName>Philips hue ({x1)</friendlyName>"
2017-07-15 14:07:30 +01:00
" <manufacturer>Royal Philips Electronics</manufacturer> "
" <modelDescription>Philips hue Personal Wireless Lighting</modelDescription> "
" <modelName>Philips hue bridge 2012</modelName> "
" <modelNumber>929000226503</modelNumber> "
2017-10-25 13:27:30 +01:00
" <serialNumber>{x3</serialNumber> "
" <UDN>uuid:{x2</UDN> "
2017-02-21 17:14:33 +00:00
" </device> "
" </root> \r \n "
" \r \n " ;
2019-04-17 20:21:56 +01:00
const char HUE_LIGHTS_STATUS_JSON1 [ ] PROGMEM =
2018-01-30 13:50:38 +00:00
" { \" on \" :{state}, "
2019-04-17 20:21:56 +01:00
" {light_status} "
2017-07-15 14:07:30 +01:00
" \" alert \" : \" none \" , "
" \" effect \" : \" none \" , "
2018-11-06 14:35:06 +00:00
" \" reachable \" :true} " ;
2018-01-30 13:14:55 +00:00
const char HUE_LIGHTS_STATUS_JSON2 [ ] PROGMEM =
" , \" type \" : \" Extended color light \" , "
2017-10-25 13:27:30 +01:00
" \" name \" : \" {j1 \" , "
2017-06-01 16:37:30 +01:00
" \" modelid \" : \" LCT007 \" , "
2017-10-25 13:27:30 +01:00
" \" uniqueid \" : \" {j2 \" , "
2018-01-30 13:14:55 +00:00
" \" swversion \" : \" 5.50.1.19085 \" } " ;
2017-07-15 14:07:30 +01:00
const char HUE_GROUP0_STATUS_JSON [ ] PROGMEM =
" { \" name \" : \" Group 0 \" , "
2017-10-25 13:27:30 +01:00
" \" lights \" :[{l1], "
2017-07-15 14:07:30 +01:00
" \" type \" : \" LightGroup \" , "
2018-01-30 13:50:38 +00:00
" \" action \" : " ;
2017-07-15 14:07:30 +01:00
// "\"scene\":\"none\",";
2017-10-18 17:22:34 +01:00
const char HueConfigResponse_JSON [ ] PROGMEM =
2017-02-21 17:14:33 +00:00
" { \" name \" : \" Philips hue \" , "
2017-10-25 13:27:30 +01:00
" \" mac \" : \" {ma \" , "
2017-02-21 17:14:33 +00:00
" \" dhcp \" :true, "
2017-10-25 13:27:30 +01:00
" \" ipaddress \" : \" {ip \" , "
" \" netmask \" : \" {ms \" , "
" \" gateway \" : \" {gw \" , "
2017-07-15 14:07:30 +01:00
" \" proxyaddress \" : \" none \" , "
2017-02-21 17:14:33 +00:00
" \" proxyport \" :0, "
2017-10-25 13:27:30 +01:00
" \" bridgeid \" : \" {br \" , "
" \" UTC \" : \" {dt \" , "
" \" whitelist \" :{ \" {id \" :{ "
" \" last use date \" : \" {dt \" , "
" \" create date \" : \" {dt \" , "
2017-02-21 17:14:33 +00:00
" \" name \" : \" Remote \" }}, "
2018-05-20 17:01:12 +01:00
" \" swversion \" : \" 01041302 \" , "
2017-09-09 14:36:30 +01:00
" \" apiversion \" : \" 1.17.0 \" , "
2017-02-21 17:14:33 +00:00
" \" swupdate \" :{ \" updatestate \" :0, \" url \" : \" \" , \" text \" : \" \" , \" notify \" : false}, "
" \" linkbutton \" :false, "
" \" portalservices \" :false "
" } " ;
2017-07-15 14:07:30 +01:00
const char HUE_LIGHT_RESPONSE_JSON [ ] PROGMEM =
2017-10-25 13:27:30 +01:00
" { \" success \" :{ \" /lights/{id/state/{cm \" :{re}} " ;
2017-02-21 17:14:33 +00:00
const char HUE_ERROR_JSON [ ] PROGMEM =
" [{ \" error \" :{ \" type \" :901, \" address \" : \" / \" , \" description \" : \" Internal Error \" }}] " ;
2017-07-15 14:07:30 +01:00
/********************************************************************************************/
2017-02-21 17:14:33 +00:00
2017-10-18 17:22:34 +01:00
String GetHueDeviceId ( uint8_t id )
2017-02-21 17:14:33 +00:00
{
2017-07-15 14:07:30 +01:00
String deviceid = WiFi . macAddress ( ) + F ( " :00:11- " ) + String ( id ) ;
deviceid . toLowerCase ( ) ;
return deviceid ; // 5c:cf:7f:13:9f:3d:00:11-1
2017-02-21 17:14:33 +00:00
}
2018-11-14 13:32:09 +00:00
String GetHueUserId ( void )
2017-02-21 17:14:33 +00:00
{
2017-07-15 14:07:30 +01:00
char userid [ 7 ] ;
2017-02-21 17:14:33 +00:00
2017-07-15 14:07:30 +01:00
snprintf_P ( userid , sizeof ( userid ) , PSTR ( " %03x " ) , ESP . getChipId ( ) ) ;
return String ( userid ) ;
2017-02-21 17:14:33 +00:00
}
2018-11-14 13:32:09 +00:00
void HandleUpnpSetupHue ( void )
2017-02-21 17:14:33 +00:00
{
2017-10-18 17:22:34 +01:00
AddLog_P ( LOG_LEVEL_DEBUG , S_LOG_HTTP , PSTR ( D_HUE_BRIDGE_SETUP ) ) ;
2017-02-21 17:14:33 +00:00
String description_xml = FPSTR ( HUE_DESCRIPTION_XML ) ;
2017-10-25 13:27:30 +01:00
description_xml . replace ( " {x1 " , WiFi . localIP ( ) . toString ( ) ) ;
description_xml . replace ( " {x2 " , HueUuid ( ) ) ;
description_xml . replace ( " {x3 " , HueSerialnumber ( ) ) ;
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_XML , description_xml ) ;
2017-02-21 17:14:33 +00:00
}
2017-10-18 17:22:34 +01:00
void HueNotImplemented ( String * path )
2017-02-21 17:14:33 +00:00
{
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s) " ) , path - > c_str ( ) ) ;
2017-07-15 14:07:30 +01:00
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_JSON , " {} " ) ;
2017-02-21 17:14:33 +00:00
}
2017-10-18 17:22:34 +01:00
void HueConfigResponse ( String * response )
2017-02-21 17:14:33 +00:00
{
2017-10-18 17:22:34 +01:00
* response + = FPSTR ( HueConfigResponse_JSON ) ;
2017-10-25 13:27:30 +01:00
response - > replace ( " {ma " , WiFi . macAddress ( ) ) ;
response - > replace ( " {ip " , WiFi . localIP ( ) . toString ( ) ) ;
response - > replace ( " {ms " , WiFi . subnetMask ( ) . toString ( ) ) ;
response - > replace ( " {gw " , WiFi . gatewayIP ( ) . toString ( ) ) ;
response - > replace ( " {br " , HueBridgeId ( ) ) ;
2018-02-17 13:09:39 +00:00
response - > replace ( " {dt " , GetDateAndTime ( DT_UTC ) ) ;
2017-10-25 13:27:30 +01:00
response - > replace ( " {id " , GetHueUserId ( ) ) ;
2017-07-15 14:07:30 +01:00
}
2017-10-18 17:22:34 +01:00
void HueConfig ( String * path )
2017-07-15 14:07:30 +01:00
{
String response = " " ;
2017-10-18 17:22:34 +01:00
HueConfigResponse ( & response ) ;
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_JSON , response ) ;
2017-07-15 14:07:30 +01:00
}
2019-04-25 12:06:35 +01:00
// device is forced to CT mode instead of HSB
// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBWC
2018-09-08 07:49:08 +01:00
bool g_gotct = false ;
2019-04-19 20:39:43 +01:00
// store previously set values from the Alexa app
2019-04-25 12:06:35 +01:00
// it allows to correct slight deviations from value set by the app
// The Alexa app is very sensitive to exact values
2019-04-19 20:39:43 +01:00
uint16_t prev_hue = 0 ;
uint8_t prev_sat = 0 ;
uint8_t prev_bri = 254 ;
uint16_t prev_ct = 254 ;
2019-04-28 10:00:54 +01:00
char prev_x_str [ 24 ] = " \0 " ; // store previously set xy by Alexa app
char prev_y_str [ 24 ] = " \0 " ;
2019-04-19 20:39:43 +01:00
2019-01-28 13:08:33 +00:00
void HueLightStatus1 ( uint8_t device , String * response )
2017-07-15 14:07:30 +01:00
{
2019-04-17 20:21:56 +01:00
uint16_t ct = 0 ;
2019-05-04 22:04:53 +01:00
uint8_t color_mode ;
2019-04-17 20:21:56 +01:00
String light_status = " " ;
2019-04-19 20:39:43 +01:00
uint16_t hue = 0 ;
uint8_t sat = 0 ;
uint8_t bri = 254 ;
2019-04-17 20:21:56 +01:00
2017-09-02 13:37:02 +01:00
2017-10-18 17:22:34 +01:00
if ( light_type ) {
2019-05-04 22:04:53 +01:00
light_state . getHSB ( & hue , & sat , nullptr ) ;
bri = light_state . getBri ( ) ;
2019-04-19 20:39:43 +01:00
if ( bri > 254 ) bri = 254 ; // Philips Hue bri is between 1 and 254
if ( bri < 1 ) bri = 1 ;
2019-04-25 12:06:35 +01:00
if ( ( bri > prev_bri ? bri - prev_bri : prev_bri - bri ) < 1 )
2019-04-19 20:39:43 +01:00
bri = prev_bri ;
if ( sat > 254 ) sat = 254 ; // Philips Hue only accepts 254 as max hue
2019-04-28 10:00:54 +01:00
if ( ( sat > prev_sat ? sat - prev_sat : prev_sat - sat ) < 1 ) {
2019-04-19 20:39:43 +01:00
sat = prev_sat ;
2019-04-28 10:00:54 +01:00
} else { // if sat was changed outside of Alexa, reset xy
prev_x_str [ 0 ] = prev_y_str [ 0 ] = 0 ;
}
2019-04-19 20:39:43 +01:00
2019-04-25 12:06:35 +01:00
hue = changeUIntScale ( hue , 0 , 359 , 0 , 65535 ) ;
2019-04-28 10:00:54 +01:00
if ( ( hue > prev_hue ? hue - prev_hue : prev_hue - hue ) < 400 ) {
2019-04-19 20:39:43 +01:00
hue = prev_hue ;
2019-04-28 10:00:54 +01:00
} else { // if hue was changed outside of Alexa, reset xy
prev_x_str [ 0 ] = prev_y_str [ 0 ] = 0 ;
}
2019-04-19 20:39:43 +01:00
2019-05-04 22:04:53 +01:00
color_mode = light_state . getColorMode ( ) ;
2019-04-25 12:06:35 +01:00
ct = light_state . getCT ( ) ;
2019-05-04 22:04:53 +01:00
if ( LCM_RGB = = color_mode ) { g_gotct = false ; }
if ( LCM_CT = = color_mode ) { g_gotct = true ; }
// If LCM_BOTH == color_mode, leave g_gotct unchanged
2019-04-25 12:06:35 +01:00
// re-adjust ct if close to command value
if ( ( ct > prev_ct ? ct - prev_ct : prev_ct - ct ) < 1 )
2019-04-19 20:39:43 +01:00
ct = prev_ct ;
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("HueLightStatus1 HSB (%d, %d, %d) Prev_HSB (%d, %d, %d)"),
// hue, sat, bri, prev_hue, prev_sat, prev_bri);
2017-07-15 14:07:30 +01:00
}
2019-04-19 20:39:43 +01:00
2019-04-17 20:21:56 +01:00
* response + = FPSTR ( HUE_LIGHTS_STATUS_JSON1 ) ;
2018-01-30 13:14:55 +00:00
response - > replace ( " {state} " , ( power & ( 1 < < ( device - 1 ) ) ) ? " true " : " false " ) ;
2019-04-25 12:06:35 +01:00
// Brightness for all devices with PWM
2019-04-27 21:48:51 +01:00
//if (LST_SINGLE <= light_subtype) {
2019-04-28 10:00:54 +01:00
light_status + = " \" bri \" : " ;
light_status + = String ( bri ) ;
light_status + = " , " ;
2019-04-27 21:48:51 +01:00
//}
2019-04-19 20:39:43 +01:00
if ( LST_COLDWARM < = light_subtype ) {
2019-04-28 10:00:54 +01:00
light_status + = F ( " \" colormode \" : \" " ) ;
light_status + = ( g_gotct ? " ct " : " hs " ) ;
light_status + = " \" , " ;
2019-04-19 20:39:43 +01:00
}
2019-04-17 20:21:56 +01:00
if ( LST_RGB < = light_subtype ) { // colors
2019-04-26 15:39:26 +01:00
if ( prev_x_str [ 0 ] & & prev_y_str [ 0 ] ) {
light_status + = " \" xy \" :[ " ;
light_status + = prev_x_str ;
light_status + = " , " ;
light_status + = prev_y_str ;
light_status + = " ], " ;
} else {
float x , y ;
2019-05-04 22:04:53 +01:00
light_state . getXY ( & x , & y ) ;
2019-04-28 10:00:54 +01:00
light_status + = " \" xy \" :[ " ;
light_status + = String ( x , 5 ) ;
light_status + = " , " ;
light_status + = String ( y , 5 ) ;
light_status + = " ], " ;
2019-04-26 15:39:26 +01:00
}
2019-04-28 10:00:54 +01:00
light_status + = " \" hue \" : " ;
light_status + = String ( hue ) ;
light_status + = " , " ;
light_status + = " \" sat \" : " ;
light_status + = String ( sat ) ;
light_status + = " , " ;
2019-04-17 20:21:56 +01:00
}
2019-04-25 12:06:35 +01:00
if ( LST_COLDWARM = = light_subtype | | LST_RGBW < = light_subtype ) { // white temp
2019-04-28 10:00:54 +01:00
light_status + = " \" ct \" : " ;
light_status + = String ( ct > 0 ? ct : 284 ) ;
light_status + = " , " ; // if no ct, default to medium white
2019-04-17 20:21:56 +01:00
}
response - > replace ( " {light_status} " , light_status ) ;
2018-01-30 13:14:55 +00:00
}
2019-01-28 13:08:33 +00:00
void HueLightStatus2 ( uint8_t device , String * response )
2018-01-30 13:14:55 +00:00
{
* response + = FPSTR ( HUE_LIGHTS_STATUS_JSON2 ) ;
response - > replace ( " {j1 " , Settings . friendlyname [ device - 1 ] ) ;
response - > replace ( " {j2 " , GetHueDeviceId ( device ) ) ;
2017-02-21 17:14:33 +00:00
}
2019-05-31 10:28:47 +01:00
// generate a unique lightId mixing local IP address and device number
2019-05-30 20:47:19 +01:00
// it is limited to 16 devices.
2019-06-02 12:21:12 +01:00
// last 24 bits of Mac address + 4 bits of local light
2019-05-31 10:28:47 +01:00
uint32_t EncodeLightId ( uint8_t idx )
{
uint8_t mac [ 6 ] ;
WiFi . macAddress ( mac ) ;
2019-05-31 12:37:51 +01:00
uint32_t id = ( mac [ 3 ] < < 20 ) | ( mac [ 4 ] < < 12 ) | ( mac [ 5 ] < < 4 ) | ( idx & 0xF ) ;
2019-05-31 10:28:47 +01:00
return id ;
}
uint32_t DecodeLightId ( uint32_t id ) {
return id & 0xF ;
2019-05-30 20:47:19 +01:00
}
2017-10-18 17:22:34 +01:00
void HueGlobalConfig ( String * path )
2017-02-21 17:14:33 +00:00
{
String response ;
2017-10-18 17:22:34 +01:00
uint8_t maxhue = ( devices_present > MAX_FRIENDLYNAMES ) ? MAX_FRIENDLYNAMES : devices_present ;
2017-02-21 17:14:33 +00:00
2017-09-26 14:10:58 +01:00
path - > remove ( 0 , 1 ) ; // cut leading / to get <id>
2017-07-15 14:07:30 +01:00
response = F ( " { \" lights \" :{ \" " ) ;
2017-10-12 10:29:40 +01:00
for ( uint8_t i = 1 ; i < = maxhue ; i + + ) {
2019-05-31 10:28:47 +01:00
response + = EncodeLightId ( i ) ;
2018-01-30 13:50:38 +00:00
response + = F ( " \" :{ \" state \" : " ) ;
2018-01-30 13:14:55 +00:00
HueLightStatus1 ( i , & response ) ;
HueLightStatus2 ( i , & response ) ;
2017-10-12 10:29:40 +01:00
if ( i < maxhue ) {
2017-07-15 14:07:30 +01:00
response + = " , \" " ;
2017-02-21 17:14:33 +00:00
}
}
response + = F ( " }, \" groups \" :{}, \" schedules \" :{}, \" config \" : " ) ;
2017-10-18 17:22:34 +01:00
HueConfigResponse ( & response ) ;
2017-02-21 17:14:33 +00:00
response + = " } " ;
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_JSON , response ) ;
2017-02-21 17:14:33 +00:00
}
2017-10-18 17:22:34 +01:00
void HueAuthentication ( String * path )
2017-02-21 17:14:33 +00:00
{
char response [ 38 ] ;
2017-09-02 13:37:02 +01:00
2017-10-18 17:22:34 +01:00
snprintf_P ( response , sizeof ( response ) , PSTR ( " [{ \" success \" :{ \" username \" : \" %s \" }}] " ) , GetHueUserId ( ) . c_str ( ) ) ;
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_JSON , response ) ;
2017-02-21 17:14:33 +00:00
}
2017-10-18 17:22:34 +01:00
void HueLights ( String * path )
2017-02-21 17:14:33 +00:00
{
/*
* http : //sonoff/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
*/
2017-07-15 14:07:30 +01:00
String response ;
2019-02-23 14:29:42 +00:00
int code = 200 ;
uint16_t tmp = 0 ;
2019-04-25 12:06:35 +01:00
uint16_t hue = 0 ;
uint8_t sat = 0 ;
uint8_t bri = 254 ;
2017-08-16 16:05:36 +01:00
uint16_t ct = 0 ;
2019-04-25 12:06:35 +01:00
bool resp = false ; // is the response non null (add comma between parameters)
2017-02-21 17:14:33 +00:00
bool on = false ;
2019-04-25 12:06:35 +01:00
bool change = false ; // need to change a parameter to the light
2019-02-23 14:29:42 +00:00
uint8_t device = 1 ;
2017-10-18 17:22:34 +01:00
uint8_t maxhue = ( devices_present > MAX_FRIENDLYNAMES ) ? MAX_FRIENDLYNAMES : devices_present ;
2017-02-21 17:14:33 +00:00
2017-09-26 14:10:58 +01:00
path - > remove ( 0 , path - > indexOf ( " /lights " ) ) ; // Remove until /lights
if ( path - > endsWith ( " /lights " ) ) { // Got /lights
2017-02-21 17:14:33 +00:00
response = " { \" " ;
2017-10-12 10:29:40 +01:00
for ( uint8_t i = 1 ; i < = maxhue ; i + + ) {
2019-05-31 10:28:47 +01:00
response + = EncodeLightId ( i ) ;
2018-01-30 13:50:38 +00:00
response + = F ( " \" :{ \" state \" : " ) ;
2018-01-30 13:14:55 +00:00
HueLightStatus1 ( i , & response ) ;
HueLightStatus2 ( i , & response ) ;
2017-10-12 10:29:40 +01:00
if ( i < maxhue ) {
2017-07-15 14:07:30 +01:00
response + = " , \" " ;
2017-02-21 17:14:33 +00:00
}
}
response + = " } " ;
}
2017-09-26 14:10:58 +01:00
else if ( path - > endsWith ( " /state " ) ) { // Got ID/state
path - > remove ( 0 , 8 ) ; // Remove /lights/
path - > remove ( path - > indexOf ( " /state " ) ) ; // Remove /state
2019-06-02 12:21:12 +01:00
device = DecodeLightId ( atoi ( path - > c_str ( ) ) ) ;
2017-10-12 10:29:40 +01:00
if ( ( device < 1 ) | | ( device > maxhue ) ) {
2017-04-25 17:24:42 +01:00
device = 1 ;
}
2018-10-24 20:54:16 +01:00
if ( WebServer - > args ( ) ) {
2017-07-15 14:07:30 +01:00
response = " [ " ;
2017-02-21 17:14:33 +00:00
StaticJsonBuffer < 400 > jsonBuffer ;
2018-10-28 10:17:41 +00:00
JsonObject & hue_json = jsonBuffer . parseObject ( WebServer - > arg ( ( WebServer - > args ( ) ) - 1 ) ) ;
2017-02-21 17:14:33 +00:00
if ( hue_json . containsKey ( " on " ) ) {
2017-09-02 13:37:02 +01:00
2017-07-15 14:07:30 +01:00
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
2019-06-02 12:21:12 +01:00
response . replace ( " {id " , String ( EncodeLightId ( device ) ) ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {cm " , " on " ) ;
2017-09-02 13:37:02 +01:00
2017-02-21 17:14:33 +00:00
on = hue_json [ " on " ] ;
switch ( on )
{
2018-05-28 14:52:42 +01:00
case false : ExecuteCommandPower ( device , POWER_OFF , SRC_HUE ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {re " , " false " ) ;
2017-02-21 17:14:33 +00:00
break ;
2018-05-28 14:52:42 +01:00
case true : ExecuteCommandPower ( device , POWER_ON , SRC_HUE ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {re " , " true " ) ;
2017-02-21 17:14:33 +00:00
break ;
2017-10-25 13:27:30 +01:00
default : response . replace ( " {re " , ( power & ( 1 < < ( device - 1 ) ) ) ? " true " : " false " ) ;
2017-02-21 17:14:33 +00:00
break ;
}
2017-07-15 14:07:30 +01:00
resp = true ;
2017-02-21 17:14:33 +00:00
}
2017-09-02 13:37:02 +01:00
2017-10-18 17:22:34 +01:00
if ( light_type ) {
2019-05-04 22:04:53 +01:00
light_state . getHSB ( & hue , & sat , nullptr ) ;
bri = light_state . getBri ( ) ; // get the combined bri for CT and RGB, not only the RGB one
2019-04-25 12:06:35 +01:00
ct = light_state . getCT ( ) ;
2019-05-04 22:04:53 +01:00
uint8_t color_mode = light_state . getColorMode ( ) ;
if ( LCM_RGB = = color_mode ) { g_gotct = false ; }
if ( LCM_CT = = color_mode ) { g_gotct = true ; }
// If LCM_BOTH == color_mode, leave g_gotct unchanged
2017-07-15 14:07:30 +01:00
}
2019-04-26 15:39:26 +01:00
prev_x_str [ 0 ] = prev_y_str [ 0 ] = 0 ; // reset xy string
2017-07-15 14:07:30 +01:00
2018-09-08 07:49:08 +01:00
if ( hue_json . containsKey ( " bri " ) ) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
2017-02-21 17:14:33 +00:00
tmp = hue_json [ " bri " ] ;
2019-04-25 12:06:35 +01:00
prev_bri = bri = tmp ; // store command value
// extend bri value if set to max
if ( 254 < = bri ) { bri = 255 ; }
if ( resp ) { response + = " , " ; }
2017-02-21 17:14:33 +00:00
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {id " , String ( device ) ) ;
response . replace ( " {cm " , " bri " ) ;
response . replace ( " {re " , String ( tmp ) ) ;
2019-04-25 12:06:35 +01:00
if ( LST_SINGLE < = light_subtype ) {
change = true ;
}
2017-07-15 14:07:30 +01:00
resp = true ;
2017-02-21 17:14:33 +00:00
}
2019-04-28 10:00:54 +01:00
// handle xy before Hue/Sat
// If the request contains both XY and HS, we wan't to give priority to HS
if ( hue_json . containsKey ( " xy " ) ) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
float x , y ;
x = hue_json [ " xy " ] [ 0 ] ;
y = hue_json [ " xy " ] [ 1 ] ;
const String & x_str = hue_json [ " xy " ] [ 0 ] ;
const String & y_str = hue_json [ " xy " ] [ 1 ] ;
x_str . toCharArray ( prev_x_str , sizeof ( prev_x_str ) ) ;
y_str . toCharArray ( prev_y_str , sizeof ( prev_y_str ) ) ;
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XY (%s %s)", String(prev_x,5).c_str(), String(prev_y,5).c_str());
uint8_t rr , gg , bb ;
LightStateClass : : XyToRgb ( x , y , & rr , & gg , & bb ) ;
LightStateClass : : RgbToHsb ( rr , gg , bb , & hue , & sat , nullptr ) ;
prev_hue = changeUIntScale ( hue , 0 , 359 , 0 , 65535 ) ; // calculate back prev_hue
prev_sat = ( sat > 254 ? 254 : sat ) ;
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
if ( resp ) { response + = " , " ; }
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
response . replace ( " {id " , String ( device ) ) ;
response . replace ( " {cm " , " xy " ) ;
response . replace ( " {re " , " [ " + x_str + " , " + y_str + " ] " ) ;
g_gotct = false ;
resp = true ;
change = true ;
}
2018-09-08 07:49:08 +01:00
if ( hue_json . containsKey ( " hue " ) ) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
2017-02-21 17:14:33 +00:00
tmp = hue_json [ " hue " ] ;
2019-04-25 12:06:35 +01:00
prev_hue = tmp ;
// change range from 0..65535 to 0..359
hue = changeUIntScale ( tmp , 0 , 65535 , 0 , 359 ) ;
if ( resp ) { response + = " , " ; }
2017-02-21 17:14:33 +00:00
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {id " , String ( device ) ) ;
response . replace ( " {cm " , " hue " ) ;
response . replace ( " {re " , String ( tmp ) ) ;
2019-04-25 12:06:35 +01:00
if ( LST_RGB < = light_subtype ) {
g_gotct = false ;
change = true ;
}
2017-07-15 14:07:30 +01:00
resp = true ;
2017-02-21 17:14:33 +00:00
}
2018-09-08 07:49:08 +01:00
if ( hue_json . containsKey ( " sat " ) ) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
2017-02-21 17:14:33 +00:00
tmp = hue_json [ " sat " ] ;
2019-04-25 12:06:35 +01:00
prev_sat = sat = tmp ; // store command value
// extend sat value if set to max
if ( 254 < = sat ) { sat = 255 ; }
if ( resp ) { response + = " , " ; }
2017-02-21 17:14:33 +00:00
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {id " , String ( device ) ) ;
response . replace ( " {cm " , " sat " ) ;
response . replace ( " {re " , String ( tmp ) ) ;
2019-04-25 12:06:35 +01:00
if ( LST_RGB < = light_subtype ) {
g_gotct = false ;
change = true ;
}
2018-09-08 07:49:08 +01:00
resp = true ;
2017-02-21 17:14:33 +00:00
}
2019-04-25 12:06:35 +01:00
if ( hue_json . containsKey ( " ct " ) ) { // Color temperature 153 (Cold) to 500 (Warm)
2017-08-16 16:05:36 +01:00
ct = hue_json [ " ct " ] ;
2019-04-19 20:39:43 +01:00
prev_ct = ct ; // store commande value
2019-04-25 12:06:35 +01:00
if ( resp ) { response + = " , " ; }
2017-08-15 21:09:52 +01:00
response + = FPSTR ( HUE_LIGHT_RESPONSE_JSON ) ;
2017-10-25 13:27:30 +01:00
response . replace ( " {id " , String ( device ) ) ;
response . replace ( " {cm " , " ct " ) ;
response . replace ( " {re " , String ( ct ) ) ;
2019-04-25 12:06:35 +01:00
if ( ( LST_COLDWARM = = light_subtype ) | | ( LST_RGBW < = light_subtype ) ) {
g_gotct = true ;
change = true ;
}
resp = true ;
2017-08-15 21:09:52 +01:00
}
2017-07-15 14:07:30 +01:00
if ( change ) {
2017-10-18 17:22:34 +01:00
if ( light_type ) {
2019-04-25 12:06:35 +01:00
if ( g_gotct ) {
2019-05-04 22:04:53 +01:00
light_controller . changeCTB ( ct , bri ) ;
2019-04-25 12:06:35 +01:00
} else {
2019-05-04 22:04:53 +01:00
light_controller . changeHSB ( hue , sat , bri ) ;
2019-04-25 12:06:35 +01:00
}
LightPreparePower ( ) ;
if ( LST_COLDWARM < = light_subtype ) {
MqttPublishPrefixTopic_P ( RESULT_OR_STAT , PSTR ( D_CMND_COLOR ) ) ;
} else {
MqttPublishPrefixTopic_P ( RESULT_OR_STAT , PSTR ( D_CMND_DIMMER ) ) ;
}
2017-07-15 14:07:30 +01:00
}
2017-04-25 17:24:42 +01:00
change = false ;
2017-02-21 17:14:33 +00:00
}
response + = " ] " ;
2017-07-15 14:07:30 +01:00
if ( 2 = = response . length ( ) ) {
response = FPSTR ( HUE_ERROR_JSON ) ;
}
2017-09-02 13:37:02 +01:00
}
2017-02-21 17:14:33 +00:00
else {
2017-07-15 14:07:30 +01:00
response = FPSTR ( HUE_ERROR_JSON ) ;
2017-02-21 17:14:33 +00:00
}
}
2017-09-26 14:10:58 +01:00
else if ( path - > indexOf ( " /lights/ " ) > = 0 ) { // Got /lights/ID
path - > remove ( 0 , 8 ) ; // Remove /lights/
2019-05-31 10:28:47 +01:00
device = DecodeLightId ( atoi ( path - > c_str ( ) ) ) ;
2017-10-12 10:29:40 +01:00
if ( ( device < 1 ) | | ( device > maxhue ) ) {
2017-04-25 17:24:42 +01:00
device = 1 ;
}
2018-01-30 13:50:38 +00:00
response + = F ( " { \" state \" : " ) ;
2018-01-30 13:14:55 +00:00
HueLightStatus1 ( device , & response ) ;
HueLightStatus2 ( device , & response ) ;
2017-07-15 14:07:30 +01:00
}
else {
2019-02-23 14:29:42 +00:00
response = " {} " ;
code = 406 ;
2017-07-15 14:07:30 +01:00
}
2019-04-25 12:06:35 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_HTTP D_HUE " Result (%s) " ) , response . c_str ( ) ) ;
2019-02-23 14:29:42 +00:00
WSSend ( code , CT_JSON , response ) ;
2017-07-15 14:07:30 +01:00
}
2017-10-18 17:22:34 +01:00
void HueGroups ( String * path )
2017-07-15 14:07:30 +01:00
{
2017-09-02 13:37:02 +01:00
/*
2017-08-15 21:09:52 +01:00
* http : //sonoff/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
*/
2017-07-15 14:07:30 +01:00
String response = " {} " ;
2017-10-18 17:22:34 +01:00
uint8_t maxhue = ( devices_present > MAX_FRIENDLYNAMES ) ? MAX_FRIENDLYNAMES : devices_present ;
2017-09-02 13:37:02 +01:00
2017-07-15 14:07:30 +01:00
if ( path - > endsWith ( " /0 " ) ) {
response = FPSTR ( HUE_GROUP0_STATUS_JSON ) ;
String lights = F ( " \" 1 \" " ) ;
2017-10-12 10:29:40 +01:00
for ( uint8_t i = 2 ; i < = maxhue ; i + + ) {
2019-04-28 10:00:54 +01:00
lights + = " , \" " ;
2019-05-31 10:28:47 +01:00
lights + = EncodeLightId ( i ) ;
2019-04-28 10:00:54 +01:00
lights + = " \" " ;
2017-02-21 17:14:33 +00:00
}
2017-10-25 13:27:30 +01:00
response . replace ( " {l1 " , lights ) ;
2018-01-30 13:14:55 +00:00
HueLightStatus1 ( 1 , & response ) ;
response + = F ( " } " ) ;
2017-02-21 17:14:33 +00:00
}
2017-09-02 13:37:02 +01:00
2019-02-23 14:29:42 +00:00
WSSend ( 200 , CT_JSON , response ) ;
2017-02-21 17:14:33 +00:00
}
2017-10-18 17:22:34 +01:00
void HandleHueApi ( String * path )
2017-02-21 17:14:33 +00:00
{
/* HUE API uses /api/<userid>/<command> syntax. The userid is created by the echo device and
* on original HUE the pressed button allows for creation of this user . We simply ignore the
2017-09-02 13:37:02 +01:00
* user part and allow every caller as with Web or WeMo .
2017-02-21 17:14:33 +00:00
*
* ( c ) Heiko Krupp , 2017
2018-10-28 15:16:18 +00:00
*
* Hue URL
* http : //sonoff/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
* is converted by webserver to
* http : //sonoff/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
2017-02-21 17:14:33 +00:00
*/
2017-09-02 13:37:02 +01:00
2017-02-21 17:14:33 +00:00
uint8_t args = 0 ;
2017-07-15 14:07:30 +01:00
path - > remove ( 0 , 4 ) ; // remove /api
uint16_t apilen = path - > length ( ) ;
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_HTTP D_HUE_API " (%s) " ) , path - > c_str ( ) ) ; // HTP: Hue API (//lights/1/state
2017-10-18 17:22:34 +01:00
for ( args = 0 ; args < WebServer - > args ( ) ; args + + ) {
String json = WebServer - > arg ( args ) ;
2019-03-08 14:15:42 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_HTTP D_HUE_POST_ARGS " (%s) " ) , json . c_str ( ) ) ; // HTP: Hue POST args ({"on":false})
2017-02-21 17:14:33 +00:00
}
2017-09-02 13:37:02 +01:00
2017-09-26 14:10:58 +01:00
if ( path - > endsWith ( " /invalid/ " ) ) { } // Just ignore
2017-10-18 17:22:34 +01:00
else if ( ! apilen ) HueAuthentication ( path ) ; // New HUE App setup
else if ( path - > endsWith ( " / " ) ) HueAuthentication ( path ) ; // New HUE App setup
else if ( path - > endsWith ( " /config " ) ) HueConfig ( path ) ;
else if ( path - > indexOf ( " /lights " ) > = 0 ) HueLights ( path ) ;
else if ( path - > indexOf ( " /groups " ) > = 0 ) HueGroups ( path ) ;
else if ( path - > endsWith ( " /schedules " ) ) HueNotImplemented ( path ) ;
else if ( path - > endsWith ( " /sensors " ) ) HueNotImplemented ( path ) ;
else if ( path - > endsWith ( " /scenes " ) ) HueNotImplemented ( path ) ;
else if ( path - > endsWith ( " /rules " ) ) HueNotImplemented ( path ) ;
else HueGlobalConfig ( path ) ;
2017-02-21 17:14:33 +00:00
}
2018-10-10 21:21:44 +01:00
2019-05-20 14:09:42 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv20 ( uint8_t function )
2018-10-10 21:21:44 +01:00
{
2019-05-20 14:09:42 +01:00
bool result = false ;
if ( devices_present & & ( EMUL_HUE = = Settings . flag2 . emulation ) ) {
switch ( function ) {
case FUNC_WEB_ADD_HANDLER :
WebServer - > on ( " /description.xml " , HandleUpnpSetupHue ) ;
break ;
2019-03-25 15:03:28 +00:00
}
2018-10-10 21:21:44 +01:00
}
2019-05-20 14:09:42 +01:00
return result ;
2018-10-10 21:21:44 +01:00
}
2019-05-20 14:09:42 +01:00
# endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE