2020-03-14 13:17:30 +00:00
/*
xdrv_23_zigbee . ino - zigbee support for Tasmota
Copyright ( C ) 2020 Theo Arends and Stephan Hadinger
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 .
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 .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_ZIGBEE
2020-03-15 13:53:05 +00:00
# if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
2020-03-14 13:17:30 +00:00
// Add global functions for Hue Emulation
// idx: index in the list of zigbee_devices
void HueLightStatus1Zigbee ( uint16_t shortaddr , uint8_t local_light_subtype , String * response ) {
2020-03-26 18:34:59 +00:00
static const char HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE [ ] PROGMEM =
" %s \" alert \" : \" none \" , "
" \" effect \" : \" none \" , "
" \" reachable \" :%s} " ;
bool power , reachable ;
uint8_t colormode , bri , sat ;
2020-03-14 13:17:30 +00:00
uint16_t ct , hue ;
uint16_t x , y ;
String light_status = " " ;
uint32_t echo_gen = findEchoGeneration ( ) ; // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
2020-08-22 17:40:44 +01:00
const Z_Device & device = zigbee_devices . findShortAddr ( shortaddr ) ;
// TODO TODO check also validity
bri = device . dimmer ;
power = device . getPower ( ) ;
colormode = device . colormode ;
sat = device . sat ;
ct = device . ct ;
hue = device . hue ;
x = device . x ;
y = device . y ;
reachable = device . getReachable ( ) ;
2020-03-14 13:17:30 +00:00
2020-03-25 19:36:57 +00:00
if ( bri > 254 ) bri = 254 ; // Philips Hue bri is between 1 and 254
if ( bri < 1 ) bri = 1 ;
if ( sat > 254 ) sat = 254 ; // Philips Hue only accepts 254 as max hue
uint16_t hue16 = changeUIntScale ( hue , 0 , 360 , 0 , 65535 ) ;
2020-03-14 13:17:30 +00:00
const size_t buf_size = 256 ;
char * buf = ( char * ) malloc ( buf_size ) ; // temp buffer for strings, avoid stack
2020-03-26 18:34:59 +00:00
snprintf_P ( buf , buf_size , PSTR ( " { \" on \" :%s, " ) , power ? " true " : " false " ) ;
2020-03-14 13:17:30 +00:00
// Brightness for all devices with PWM
if ( ( 1 = = echo_gen ) | | ( LST_SINGLE < = local_light_subtype ) ) { // force dimmer for 1st gen Echo
snprintf_P ( buf , buf_size , PSTR ( " %s \" bri \" :%d, " ) , buf , bri ) ;
}
if ( LST_COLDWARM < = local_light_subtype ) {
snprintf_P ( buf , buf_size , PSTR ( " %s \" colormode \" : \" %s \" , " ) , buf , ( 0 = = colormode ) ? " hs " : ( 1 = = colormode ) ? " xy " : " ct " ) ;
}
if ( LST_RGB < = local_light_subtype ) { // colors
if ( prev_x_str [ 0 ] & & prev_y_str [ 0 ] ) {
snprintf_P ( buf , buf_size , PSTR ( " %s \" xy \" :[%s,%s], " ) , buf , prev_x_str , prev_y_str ) ;
} else {
float x_f = x / 65536.0f ;
float y_f = y / 65536.0f ;
snprintf_P ( buf , buf_size , PSTR ( " %s \" xy \" :[%s,%s], " ) , buf , String ( x , 5 ) . c_str ( ) , String ( y , 5 ) . c_str ( ) ) ;
}
2020-03-25 19:36:57 +00:00
snprintf_P ( buf , buf_size , PSTR ( " %s \" hue \" :%d, \" sat \" :%d, " ) , buf , hue16 , sat ) ;
2020-03-14 13:17:30 +00:00
}
if ( LST_COLDWARM = = local_light_subtype | | LST_RGBW < = local_light_subtype ) { // white temp
snprintf_P ( buf , buf_size , PSTR ( " %s \" ct \" :%d, " ) , buf , ct > 0 ? ct : 284 ) ;
}
2020-03-26 18:34:59 +00:00
snprintf_P ( buf , buf_size , HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE , buf , reachable ? " true " : " false " ) ;
2020-03-14 13:17:30 +00:00
* response + = buf ;
free ( buf ) ;
}
void HueLightStatus2Zigbee ( uint16_t shortaddr , String * response )
{
2020-05-10 18:10:00 +01:00
const size_t buf_size = 300 ;
2020-03-14 13:17:30 +00:00
char * buf = ( char * ) malloc ( buf_size ) ;
2020-08-22 17:40:44 +01:00
const Z_Device & device = zigbee_devices . findShortAddr ( shortaddr ) ;
const char * friendlyName = device . friendlyName ;
const char * modelId = device . modelId ;
const char * manufacturerId = device . manufacturerId ;
2020-03-14 13:17:30 +00:00
char shortaddrname [ 8 ] ;
snprintf_P ( shortaddrname , sizeof ( shortaddrname ) , PSTR ( " 0x%04X " ) , shortaddr ) ;
snprintf_P ( buf , buf_size , HUE_LIGHTS_STATUS_JSON2 ,
2020-05-24 08:57:11 +01:00
( friendlyName ) ? EscapeJSONString ( friendlyName ) . c_str ( ) : shortaddrname ,
( modelId ) ? EscapeJSONString ( modelId ) . c_str ( ) : PSTR ( " Unknown " ) ,
( manufacturerId ) ? EscapeJSONString ( manufacturerId ) . c_str ( ) : PSTR ( " Tasmota " ) ,
2020-03-14 13:17:30 +00:00
GetHueDeviceId ( shortaddr ) . c_str ( ) ) ;
2020-05-10 18:10:00 +01:00
2020-03-14 13:17:30 +00:00
* response + = buf ;
free ( buf ) ;
}
void ZigbeeHueStatus ( String * response , uint16_t shortaddr ) {
* response + = F ( " { \" state \" : " ) ;
HueLightStatus1Zigbee ( shortaddr , zigbee_devices . getHueBulbtype ( shortaddr ) , response ) ;
HueLightStatus2Zigbee ( shortaddr , response ) ;
}
void ZigbeeCheckHue ( String * response , bool & appending ) {
uint32_t zigbee_num = zigbee_devices . devicesSize ( ) ;
for ( uint32_t i = 0 ; i < zigbee_num ; i + + ) {
2020-08-20 07:25:53 +01:00
uint16_t shortaddr = zigbee_devices . devicesAt ( i ) . shortaddr ;
int8_t bulbtype = zigbee_devices . getHueBulbtype ( shortaddr ) ;
2020-03-14 13:17:30 +00:00
if ( bulbtype > = 0 ) {
// this bulb is advertized
if ( appending ) { * response + = " , " ; }
* response + = " \" " ;
* response + = EncodeLightId ( 0 , shortaddr ) ;
* response + = F ( " \" :{ \" state \" : " ) ;
HueLightStatus1Zigbee ( shortaddr , bulbtype , response ) ; // TODO
HueLightStatus2Zigbee ( shortaddr , response ) ;
appending = true ;
}
}
}
void ZigbeeHueGroups ( String * lights ) {
uint32_t zigbee_num = zigbee_devices . devicesSize ( ) ;
for ( uint32_t i = 0 ; i < zigbee_num ; i + + ) {
2020-08-20 07:25:53 +01:00
uint16_t shortaddr = zigbee_devices . devicesAt ( i ) . shortaddr ;
int8_t bulbtype = zigbee_devices . getHueBulbtype ( shortaddr ) ;
2020-03-14 13:17:30 +00:00
if ( bulbtype > = 0 ) {
* lights + = " , \" " ;
* lights + = EncodeLightId ( i ) ;
* lights + = " \" " ;
}
}
}
// Send commands
// Power On/Off
2020-03-26 18:34:59 +00:00
void ZigbeeHuePower ( uint16_t shortaddr , bool power ) {
2020-03-30 18:23:06 +01:00
zigbeeZCLSendStr ( shortaddr , 0 , 0 , true , 0 , 0x0006 , power ? 1 : 0 , " " ) ;
2020-08-22 17:40:44 +01:00
zigbee_devices . getShortAddr ( shortaddr ) . setPower ( power ) ;
2020-03-14 13:17:30 +00:00
}
// Dimmer
void ZigbeeHueDimmer ( uint16_t shortaddr , uint8_t dimmer ) {
2020-03-22 18:14:11 +00:00
if ( dimmer > 0xFE ) { dimmer = 0xFE ; }
2020-03-14 13:17:30 +00:00
char param [ 8 ] ;
snprintf_P ( param , sizeof ( param ) , PSTR ( " %02X0A00 " ) , dimmer ) ;
2020-03-30 18:23:06 +01:00
zigbeeZCLSendStr ( shortaddr , 0 , 0 , true , 0 , 0x0008 , 0x04 , param ) ;
2020-08-22 17:40:44 +01:00
zigbee_devices . getShortAddr ( shortaddr ) . dimmer = dimmer ;
2020-03-14 13:17:30 +00:00
}
// CT
void ZigbeeHueCT ( uint16_t shortaddr , uint16_t ct ) {
2020-03-22 18:14:11 +00:00
if ( ct > 0xFEFF ) { ct = 0xFEFF ; }
2020-03-14 13:17:30 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( " ZigbeeHueCT 0x%04X - %d " ) , shortaddr , ct ) ;
char param [ 12 ] ;
snprintf_P ( param , sizeof ( param ) , PSTR ( " %02X%02X0A00 " ) , ct & 0xFF , ct > > 8 ) ;
uint8_t colormode = 2 ; // "ct"
2020-03-30 18:23:06 +01:00
zigbeeZCLSendStr ( shortaddr , 0 , 0 , true , 0 , 0x0300 , 0x0A , param ) ;
2020-08-22 17:40:44 +01:00
Z_Device & device = zigbee_devices . getShortAddr ( shortaddr ) ;
device . colormode = colormode ;
device . ct = ct ;
2020-03-14 13:17:30 +00:00
}
// XY
void ZigbeeHueXY ( uint16_t shortaddr , uint16_t x , uint16_t y ) {
char param [ 16 ] ;
2020-03-22 18:14:11 +00:00
if ( x > 0xFEFF ) { x = 0xFEFF ; }
if ( y > 0xFEFF ) { y = 0xFEFF ; }
2020-03-14 13:17:30 +00:00
snprintf_P ( param , sizeof ( param ) , PSTR ( " %02X%02X%02X%02X0A00 " ) , x & 0xFF , x > > 8 , y & 0xFF , y > > 8 ) ;
uint8_t colormode = 1 ; // "xy"
2020-03-30 18:23:06 +01:00
zigbeeZCLSendStr ( shortaddr , 0 , 0 , true , 0 , 0x0300 , 0x07 , param ) ;
2020-08-22 17:40:44 +01:00
Z_Device & device = zigbee_devices . getShortAddr ( shortaddr ) ;
device . colormode = colormode ;
device . x = x ;
device . y = y ;
2020-03-14 13:17:30 +00:00
}
// HueSat
void ZigbeeHueHS ( uint16_t shortaddr , uint16_t hue , uint8_t sat ) {
char param [ 16 ] ;
uint8_t hue8 = changeUIntScale ( hue , 0 , 360 , 0 , 254 ) ;
2020-03-22 18:14:11 +00:00
if ( sat > 0xFE ) { sat = 0xFE ; }
2020-03-25 19:36:57 +00:00
snprintf_P ( param , sizeof ( param ) , PSTR ( " %02X%02X0000 " ) , hue8 , sat ) ;
2020-03-14 13:17:30 +00:00
uint8_t colormode = 0 ; // "hs"
2020-03-30 18:23:06 +01:00
zigbeeZCLSendStr ( shortaddr , 0 , 0 , true , 0 , 0x0300 , 0x06 , param ) ;
2020-08-22 17:40:44 +01:00
Z_Device device = zigbee_devices . getShortAddr ( shortaddr ) ;
device . colormode = colormode ;
device . sat = sat ;
device . hue = hue ;
2020-03-14 13:17:30 +00:00
}
void ZigbeeHandleHue ( uint16_t shortaddr , uint32_t device_id , String & response ) {
uint8_t power , colormode , bri , sat ;
uint16_t ct , hue ;
float x , y ;
int code = 200 ;
bool resp = false ; // is the response non null (add comma between parameters)
bool on = false ;
uint8_t bulbtype = zigbee_devices . getHueBulbtype ( shortaddr ) ;
const size_t buf_size = 100 ;
char * buf = ( char * ) malloc ( buf_size ) ;
2020-04-15 08:58:38 +01:00
if ( Webserver - > args ( ) ) {
2020-03-14 13:17:30 +00:00
response = " [ " ;
StaticJsonBuffer < 300 > jsonBuffer ;
2020-04-15 08:58:38 +01:00
JsonObject & hue_json = jsonBuffer . parseObject ( Webserver - > arg ( ( Webserver - > args ( ) ) - 1 ) ) ;
2020-03-14 13:17:30 +00:00
if ( hue_json . containsKey ( " on " ) ) {
on = hue_json [ " on " ] ;
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/on \" :%s}} " ) ,
device_id , on ? " true " : " false " ) ;
2020-04-17 16:52:44 +01:00
if ( on ) {
ZigbeeHuePower ( shortaddr , 0x01 ) ;
} else {
ZigbeeHuePower ( shortaddr , 0x00 ) ;
2020-03-14 13:17:30 +00:00
}
response + = buf ;
resp = true ;
}
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.
bri = hue_json [ " bri " ] ;
prev_bri = bri ; // store command value
if ( resp ) { response + = " , " ; }
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/%s \" :%d}} " ) ,
device_id , " bri " , bri ) ;
response + = buf ;
if ( LST_SINGLE < = bulbtype ) {
// extend bri value if set to max
if ( 254 < = bri ) { bri = 255 ; }
ZigbeeHueDimmer ( shortaddr , bri ) ;
}
resp = true ;
}
// 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 " ) ) {
float x = hue_json [ " xy " ] [ 0 ] ;
float 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 ) ) ;
if ( resp ) { response + = " , " ; }
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/xy \" :[%s,%s]}} " ) ,
device_id , prev_x_str , prev_y_str ) ;
response + = buf ;
resp = true ;
uint16_t xi = x * 65536.0f ;
uint16_t yi = y * 65536.0f ;
ZigbeeHueXY ( shortaddr , xi , yi ) ;
}
bool huesat_changed = false ;
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.
hue = hue_json [ " hue " ] ;
prev_hue = hue ;
if ( resp ) { response + = " , " ; }
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/%s \" :%d}} " ) ,
device_id , " hue " , hue ) ;
response + = buf ;
if ( LST_RGB < = bulbtype ) {
2020-03-25 19:36:57 +00:00
// change range from 0..65535 to 0..360
hue = changeUIntScale ( hue , 0 , 65535 , 0 , 360 ) ;
2020-03-14 13:17:30 +00:00
huesat_changed = true ;
}
resp = true ;
}
if ( hue_json . containsKey ( " sat " ) ) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
sat = hue_json [ " sat " ] ;
prev_sat = sat ; // store command value
if ( resp ) { response + = " , " ; }
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/%s \" :%d}} " ) ,
device_id , " sat " , sat ) ;
response + = buf ;
if ( LST_RGB < = bulbtype ) {
// extend sat value if set to max
if ( 254 < = sat ) { sat = 255 ; }
huesat_changed = true ;
}
if ( huesat_changed ) {
ZigbeeHueHS ( shortaddr , hue , sat ) ;
}
resp = true ;
}
if ( hue_json . containsKey ( " ct " ) ) { // Color temperature 153 (Cold) to 500 (Warm)
ct = hue_json [ " ct " ] ;
prev_ct = ct ; // store commande value
if ( resp ) { response + = " , " ; }
snprintf_P ( buf , buf_size ,
PSTR ( " { \" success \" :{ \" /lights/%d/state/%s \" :%d}} " ) ,
device_id , " ct " , ct ) ;
response + = buf ;
if ( ( LST_COLDWARM = = bulbtype ) | | ( LST_RGBW < = bulbtype ) ) {
ZigbeeHueCT ( shortaddr , ct ) ;
}
resp = true ;
}
response + = " ] " ;
if ( 2 = = response . length ( ) ) {
response = FPSTR ( HUE_ERROR_JSON ) ;
}
}
else {
response = FPSTR ( HUE_ERROR_JSON ) ;
}
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_HTTP D_HUE " Result (%s) " ) , response . c_str ( ) ) ;
WSSend ( code , CT_JSON , response ) ;
free ( buf ) ;
}
2020-03-15 13:53:05 +00:00
# endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
# endif // USE_ZIGBEE