2019-08-31 20:23:32 +01:00
/*
2019-10-27 10:13:24 +00:00
xdrv_23_zigbee . ino - zigbee support for Tasmota
2019-08-31 20:23:32 +01:00
2019-12-31 13:23:34 +00:00
Copyright ( C ) 2020 Theo Arends and Stephan Hadinger
2019-08-31 20:23:32 +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 .
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
# define XDRV_23 23
2020-01-25 16:42:53 +00:00
const char kZbCommands [ ] PROGMEM = D_PRFX_ZB " | " // prefix
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
D_CMND_ZIGBEEZNPSEND " | " D_CMND_ZIGBEEZNPRECEIVE " | "
# endif // USE_ZIGBEE_ZNP
# ifdef USE_ZIGBEE_EZSP
2020-07-10 19:15:12 +01:00
D_CMND_ZIGBEE_EZSP_SEND " | " D_CMND_ZIGBEE_EZSP_RECEIVE " | " D_CMND_ZIGBEE_EZSP_LISTEN " | "
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_EZSP
D_CMND_ZIGBEE_PERMITJOIN " | "
D_CMND_ZIGBEE_STATUS " | " D_CMND_ZIGBEE_RESET " | " D_CMND_ZIGBEE_SEND " | " D_CMND_ZIGBEE_PROBE " | "
2020-03-01 10:25:59 +00:00
D_CMND_ZIGBEE_FORGET " | " D_CMND_ZIGBEE_SAVE " | " D_CMND_ZIGBEE_NAME " | "
2020-03-26 19:58:59 +00:00
D_CMND_ZIGBEE_BIND " | " D_CMND_ZIGBEE_UNBIND " | " D_CMND_ZIGBEE_PING " | " D_CMND_ZIGBEE_MODELID " | "
2020-04-11 17:50:46 +01:00
D_CMND_ZIGBEE_LIGHT " | " D_CMND_ZIGBEE_RESTORE " | " D_CMND_ZIGBEE_BIND_STATE " | "
2020-10-07 19:04:33 +01:00
D_CMND_ZIGBEE_CONFIG " | " D_CMND_ZIGBEE_DATA
2020-03-01 10:25:59 +00:00
;
2019-08-31 20:23:32 +01:00
2019-11-24 11:24:35 +00:00
void ( * const ZigbeeCommand [ ] ) ( void ) PROGMEM = {
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
& CmndZbZNPSend , & CmndZbZNPReceive ,
# endif // USE_ZIGBEE_ZNP
# ifdef USE_ZIGBEE_EZSP
2020-07-10 19:15:12 +01:00
& CmndZbEZSPSend , & CmndZbEZSPReceive , & CmndZbEZSPListen ,
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_EZSP
& CmndZbPermitJoin ,
& CmndZbStatus , & CmndZbReset , & CmndZbSend , & CmndZbProbe ,
2020-03-01 10:25:59 +00:00
& CmndZbForget , & CmndZbSave , & CmndZbName ,
2020-03-26 19:58:59 +00:00
& CmndZbBind , & CmndZbUnbind , & CmndZbPing , & CmndZbModelId ,
2020-03-30 18:23:06 +01:00
& CmndZbLight , & CmndZbRestore , & CmndZbBindState ,
2020-10-07 19:04:33 +01:00
& CmndZbConfig , CmndZbData ,
2019-12-23 15:53:54 +00:00
} ;
2019-08-31 20:23:32 +01:00
/********************************************************************************************/
2020-03-23 21:46:26 +00:00
// Initialize internal structures
2019-08-31 20:23:32 +01:00
void ZigbeeInit ( void )
{
2020-10-07 19:04:33 +01:00
// #pragma GCC diagnostic push
// #pragma GCC diagnostic ignored "-Winvalid-offsetof"
// Serial.printf(">>> offset %d %d %d\n", Z_offset(Z_Data_Light, dimmer), Z_offset(Z_Data_Light, x), Z_offset(Z_Data_Thermo, temperature));
// #pragma GCC diagnostic pop
2020-05-17 17:33:42 +01:00
// Check if settings in Flash are set
2020-07-29 09:02:04 +01:00
if ( PinUsed ( GPIO_ZIGBEE_RX ) & & PinUsed ( GPIO_ZIGBEE_TX ) ) {
if ( 0 = = Settings . zb_channel ) {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Randomizing Zigbee parameters, please check with 'ZbConfig' " ) ) ;
uint64_t mac64 = 0 ; // stuff mac address into 64 bits
WiFi . macAddress ( ( uint8_t * ) & mac64 ) ;
2020-07-31 10:35:26 +01:00
uint32_t esp_id = ESP_getChipId ( ) ;
# ifdef ESP8266
2020-07-31 10:37:12 +01:00
uint32_t flash_id = ESP . getFlashChipId ( ) ;
2020-07-31 10:35:26 +01:00
# else // ESP32
uint32_t flash_id = 0 ;
# endif // ESP8266 or ESP32
2020-07-29 09:02:04 +01:00
uint16_t pan_id = ( mac64 & 0x3FFF ) ;
if ( 0x0000 = = pan_id ) { pan_id = 0x0001 ; } // avoid extreme values
if ( 0x3FFF = = pan_id ) { pan_id = 0x3FFE ; } // avoid extreme values
Settings . zb_pan_id = pan_id ;
Settings . zb_ext_panid = 0xCCCCCCCC00000000L | ( mac64 & 0x00000000FFFFFFFFL ) ;
Settings . zb_precfgkey_l = ( mac64 < < 32 ) | ( esp_id < < 16 ) | flash_id ;
Settings . zb_precfgkey_h = ( mac64 < < 32 ) | ( esp_id < < 16 ) | flash_id ;
Settings . zb_channel = USE_ZIGBEE_CHANNEL ;
Settings . zb_txradio_dbm = USE_ZIGBEE_TXRADIO_DBM ;
}
2020-10-10 15:19:37 +01:00
if ( Settings . zb_txradio_dbm < 0 ) {
Settings . zb_txradio_dbm = - Settings . zb_txradio_dbm ;
2020-10-10 19:42:48 +01:00
# ifdef USE_ZIGBEE_EZSP
2020-10-10 15:19:37 +01:00
EZ_reset_config = true ; // force reconfigure of EZSP
2020-10-10 19:42:48 +01:00
# endif
2020-10-10 15:19:37 +01:00
SettingsSave ( 2 ) ;
}
2020-04-11 17:50:46 +01:00
}
2020-07-02 21:56:37 +01:00
2020-04-11 17:50:46 +01:00
// update commands with the current settings
2020-07-02 21:56:37 +01:00
# ifdef USE_ZIGBEE_ZNP
ZNP_UpdateConfig ( Settings . zb_channel , Settings . zb_pan_id , Settings . zb_ext_panid , Settings . zb_precfgkey_l , Settings . zb_precfgkey_h ) ;
# endif
# ifdef USE_ZIGBEE_EZSP
EZ_UpdateConfig ( Settings . zb_channel , Settings . zb_pan_id , Settings . zb_ext_panid , Settings . zb_precfgkey_l , Settings . zb_precfgkey_h , Settings . zb_txradio_dbm ) ;
# endif
2020-04-11 17:50:46 +01:00
2020-06-19 19:54:37 +01:00
ZigbeeInitSerial ( ) ;
2019-08-31 20:23:32 +01:00
}
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
2020-03-23 21:46:26 +00:00
// Do a factory reset of the CC2530
2019-11-24 11:24:35 +00:00
const unsigned char ZIGBEE_FACTORY_RESET [ ] PROGMEM =
2019-11-03 11:41:44 +00:00
{ Z_SREQ | Z_SAPI , SAPI_WRITE_CONFIGURATION , CONF_STARTUP_OPTION , 0x01 /* len */ , 0x01 /* STARTOPT_CLEAR_CONFIG */ } ;
//"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_ZNP
2020-02-02 19:53:49 +00:00
void CmndZbReset ( void ) {
2019-10-13 11:56:52 +01:00
if ( ZigbeeSerial ) {
switch ( XdrvMailbox . payload ) {
case 1 :
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
2019-10-13 11:56:52 +01:00
ZigbeeZNPSend ( ZIGBEE_FACTORY_RESET , sizeof ( ZIGBEE_FACTORY_RESET ) ) ;
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_ZNP
2020-01-17 23:02:01 +00:00
eraseZigbeeDevices ( ) ;
2020-10-10 15:19:37 +01:00
case 2 : // fall through
Settings . zb_txradio_dbm = - abs ( Settings . zb_txradio_dbm ) ;
2019-10-13 11:56:52 +01:00
restart_flag = 2 ;
2020-10-10 15:19:37 +01:00
# ifdef USE_ZIGBEE_ZNP
2020-03-16 17:55:58 +00:00
ResponseCmndChar_P ( PSTR ( D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING ) ) ;
2020-10-10 15:19:37 +01:00
# endif // USE_ZIGBEE_ZNP
# ifdef USE_ZIGBEE_EZSP
ResponseCmndChar_P ( PSTR ( D_JSON_ZIGBEE_EZSP " " D_JSON_RESET_AND_RESTARTING ) ) ;
# endif // USE_ZIGBEE_EZSP
2019-10-13 11:56:52 +01:00
break ;
default :
2020-10-10 15:19:37 +01:00
ResponseCmndChar_P ( PSTR ( " 1 or 2 to reset " ) ) ;
2019-10-13 11:56:52 +01:00
}
}
}
2020-03-23 21:46:26 +00:00
/********************************************************************************************/
2020-04-22 15:07:52 +01:00
//
2020-03-23 21:46:26 +00:00
// High-level function
// Send a command specified as an HEX string for the workload.
// The target endpoint is computed if zero, i.e. sent to the first known endpoint of the device.
//
// Inputs:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - endpoint: 8-bits target endpoint (source is always 0x01), if 0x00, it will be guessed from ZbStatus information (basically the first endpoint of the device)
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - clusterIf: 16-bits cluster number
// - param: pointer to HEX string for payload, should not be nullptr
// Returns: None
//
2020-03-30 18:23:06 +01:00
void zigbeeZCLSendStr ( uint16_t shortaddr , uint16_t groupaddr , uint8_t endpoint , bool clusterSpecific , uint16_t manuf ,
2020-02-23 15:46:00 +00:00
uint16_t cluster , uint8_t cmd , const char * param ) {
size_t size = param ? strlen ( param ) : 0 ;
2019-10-13 11:56:52 +01:00
SBuffer buf ( ( size + 2 ) / 2 ) ; // actual bytes buffer for data
2020-02-23 15:46:00 +00:00
if ( param ) {
while ( * param ) {
uint8_t code = parseHex_P ( & param , 2 ) ;
buf . add8 ( code ) ;
}
2019-10-13 11:56:52 +01:00
}
2020-05-17 17:33:42 +01:00
if ( ( 0 = = endpoint ) & & ( BAD_SHORTADDR ! = shortaddr ) ) {
2020-03-14 13:17:30 +00:00
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address
2020-03-17 17:46:05 +00:00
endpoint = zigbee_devices . findFirstEndpoint ( shortaddr ) ;
2020-03-23 21:46:26 +00:00
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
2019-10-13 11:56:52 +01:00
}
2020-03-14 13:17:30 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s " ) ,
shortaddr , groupaddr , cluster , endpoint , cmd , param ) ;
2019-10-13 11:56:52 +01:00
2020-05-17 17:33:42 +01:00
if ( ( 0 = = endpoint ) & & ( BAD_SHORTADDR ! = shortaddr ) ) { // endpoint null is ok for group address
2020-01-25 16:42:53 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( " ZbSend: unspecified endpoint " ) ) ;
2019-10-13 11:56:52 +01:00
return ;
2020-03-23 21:46:26 +00:00
}
2019-10-13 11:56:52 +01:00
// everything is good, we can send the command
2020-09-06 19:51:20 +01:00
uint8_t seq = zigbee_devices . getNextSeqNumber ( shortaddr ) ;
ZigbeeZCLSend_Raw ( ZigbeeZCLSendMessage ( {
shortaddr ,
groupaddr ,
cluster /*cluster*/ ,
endpoint ,
cmd ,
manuf , /* manuf */
clusterSpecific /* not cluster specific */ ,
true /* response */ ,
2020-10-19 19:34:40 +01:00
false /* discover route */ ,
2020-09-06 19:51:20 +01:00
seq , /* zcl transaction id */
buf . getBuffer ( ) , buf . len ( )
} ) ) ;
2019-12-15 15:02:41 +00:00
// now set the timer, if any, to read back the state later
if ( clusterSpecific ) {
2020-08-01 17:52:04 +01:00
# ifndef USE_ZIGBEE_NO_READ_ATTRIBUTES // read back attribute value unless it is disabled
2020-09-06 19:51:20 +01:00
sendHueUpdate ( shortaddr , groupaddr , cluster , endpoint ) ;
2020-08-01 17:52:04 +01:00
# endif
2019-12-15 15:02:41 +00:00
}
2019-10-13 11:56:52 +01:00
}
2020-08-08 11:17:37 +01:00
// Special encoding for multiplier:
// multiplier == 0: ignore
// multiplier == 1: ignore
// multiplier > 0: divide by the multiplier
// multiplier < 0: multiply by the -multiplier (positive)
2020-08-20 07:25:53 +01:00
void ZbApplyMultiplier ( double & val_d , int8_t multiplier ) {
2020-08-08 11:17:37 +01:00
if ( ( 0 ! = multiplier ) & & ( 1 ! = multiplier ) ) {
if ( multiplier > 0 ) { // inverse of decoding
val_d = val_d / multiplier ;
} else {
val_d = val_d * ( - multiplier ) ;
}
}
}
2020-10-13 21:45:11 +01:00
//
// Write Tuya-Moes attribute
//
bool ZbTuyaWrite ( SBuffer & buf , const Z_attribute & attr , uint8_t transid ) {
double val_d = attr . getFloat ( ) ;
const char * val_str = attr . getStr ( ) ;
if ( attr . key_is_str ) { return false ; } // couldn't find attr if so skip
if ( attr . isNum ( ) & & ( 1 ! = attr . attr_multiplier ) ) {
ZbApplyMultiplier ( val_d , attr . attr_multiplier ) ;
}
buf . add8 ( 0 ) ; // status
buf . add8 ( transid ) ; // transid
buf . add16 ( attr . key . id . attr_id ) ; // dp
buf . add8 ( 0 ) ; // fn
int32_t res = encodeSingleAttribute ( buf , val_d , val_str , attr . attr_type ) ;
if ( res < 0 ) {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Error for Tuya attribute type %04X/%04X '0x%02X' " ) , attr . key . id . cluster , attr . key . id . attr_id , attr . attr_type ) ;
return false ;
}
return true ;
}
2020-09-14 21:06:19 +01:00
//
// Send Attribute Write, apply mutlipliers before
//
bool ZbAppendWriteBuf ( SBuffer & buf , const Z_attribute & attr , bool prepend_status_ok ) {
double val_d = attr . getFloat ( ) ;
const char * val_str = attr . getStr ( ) ;
2020-10-13 21:45:11 +01:00
if ( attr . key_is_str ) { return false ; } // couldn't find attr if so skip
2020-09-14 21:06:19 +01:00
if ( attr . isNum ( ) & & ( 1 ! = attr . attr_multiplier ) ) {
ZbApplyMultiplier ( val_d , attr . attr_multiplier ) ;
}
// push the value in the buffer
buf . add16 ( attr . key . id . attr_id ) ; // prepend with attribute identifier
if ( prepend_status_ok ) {
buf . add8 ( Z_SUCCESS ) ; // status OK = 0x00
}
buf . add8 ( attr . attr_type ) ; // prepend with attribute type
int32_t res = encodeSingleAttribute ( buf , val_d , val_str , attr . attr_type ) ;
if ( res < 0 ) {
// remove the attribute type we just added
// buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3));
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Unsupported attribute type %04X/%04X '0x%02X' " ) , attr . key . id . cluster , attr . key . id . attr_id , attr . attr_type ) ;
return false ;
}
return true ;
}
2020-10-13 21:45:11 +01:00
//
// Parse "Report", "Write", "Response" or "Config" attribute
2020-06-03 21:39:04 +01:00
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
2020-10-13 21:45:11 +01:00
//
void ZbSendReportWrite ( class JsonParserToken val_pubwrite , class ZigbeeZCLSendMessage & packet ) {
2020-05-29 21:52:45 +01:00
SBuffer buf ( 200 ) ; // buffer to store the binary output of attibutes
2020-06-03 21:39:04 +01:00
if ( nullptr = = XdrvMailbox . command ) {
XdrvMailbox . command = ( char * ) " " ; // prevent a crash when calling ReponseCmndChar and there was no previous command
}
2020-05-29 21:52:45 +01:00
// iterate on keys
2020-09-21 20:49:32 +01:00
for ( auto key : val_pubwrite . getObject ( ) ) {
JsonParserToken value = key . getValue ( ) ;
2020-05-29 21:52:45 +01:00
2020-09-14 21:06:19 +01:00
Z_attribute attr ;
2020-09-21 20:49:32 +01:00
attr . setKeyName ( key . getStr ( ) ) ;
2020-09-14 21:06:19 +01:00
if ( Z_parseAttributeKey ( attr ) ) {
// Buffer ready, do some sanity checks
2020-10-13 21:45:11 +01:00
// all attributes must use the same cluster
if ( 0xFFFF = = packet . cluster ) {
packet . cluster = attr . key . id . cluster ; // set the cluster for this packet
} else if ( packet . cluster ! = attr . key . id . cluster ) {
2020-09-14 21:06:19 +01:00
ResponseCmndChar_P ( PSTR ( " No more than one cluster id per command " ) ) ;
return ;
2020-05-29 21:52:45 +01:00
}
2020-09-14 21:06:19 +01:00
} else {
if ( attr . key_is_str ) {
Response_P ( PSTR ( " { \" %s \" : \" %s'%s' \" } " ) , XdrvMailbox . command , PSTR ( " Unknown attribute " ) , key ) ;
return ;
}
if ( Zunk = = attr . attr_type ) {
Response_P ( PSTR ( " { \" %s \" : \" %s'%s' \" } " ) , XdrvMailbox . command , PSTR ( " Unknown attribute type for attribute " ) , key ) ;
return ;
2020-05-29 21:52:45 +01:00
}
}
2020-10-13 21:45:11 +01:00
// copy value from input to attribute, in numerical or string format
2020-09-21 20:49:32 +01:00
if ( value . isStr ( ) ) {
attr . setStr ( value . getStr ( ) ) ;
} else if ( value . isNum ( ) ) {
attr . setFloat ( value . getFloat ( ) ) ;
2020-05-29 21:52:45 +01:00
}
2020-09-14 21:06:19 +01:00
double val_d = 0 ; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
const char * val_str = " " ; // variant as string
////////////////////////////////////////////////////////////////////////////////
2020-08-08 11:17:37 +01:00
// Split encoding depending on message
2020-10-13 21:45:11 +01:00
if ( packet . cmd ! = ZCL_CONFIGURE_REPORTING ) {
if ( ( packet . cluster = = 0 XEF00 ) & & ( packet . cmd = = ZCL_WRITE_ATTRIBUTES ) ) {
// special case of Tuya / Moes attributes
if ( buf . len ( ) > 0 ) {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Only 1 attribute allowed for Tuya " ) ) ;
return ;
}
packet . clusterSpecific = true ;
packet . cmd = 0x00 ;
if ( ! ZbTuyaWrite ( buf , attr , zigbee_devices . getNextSeqNumber ( packet . shortaddr ) ) ) {
return ; // error
}
} else if ( ! ZbAppendWriteBuf ( buf , attr , packet . cmd = = ZCL_READ_ATTRIBUTES_RESPONSE ) ) { // general case
2020-09-14 21:06:19 +01:00
return ; // error
2020-08-08 11:17:37 +01:00
}
} else {
// ////////////////////////////////////////////////////////////////////////////////
// ZCL_CONFIGURE_REPORTING
2020-09-21 20:49:32 +01:00
if ( ! value . isObject ( ) ) {
2020-08-08 11:17:37 +01:00
ResponseCmndChar_P ( PSTR ( " Config requires JSON objects " ) ) ;
return ;
}
2020-09-21 20:49:32 +01:00
JsonParserObject attr_config = value . getObject ( ) ;
2020-08-08 11:17:37 +01:00
bool attr_direction = false ;
2020-09-21 20:49:32 +01:00
uint32_t dir = attr_config . getUInt ( PSTR ( " DirectionReceived " ) , 0 ) ;
if ( dir ) { attr_direction = true ; }
2020-08-08 11:17:37 +01:00
// read MinInterval and MaxInterval, default to 0xFFFF if not specified
2020-09-21 20:49:32 +01:00
uint16_t attr_min_interval = attr_config . getUInt ( PSTR ( " MinInterval " ) , 0xFFFF ) ;
uint16_t attr_max_interval = attr_config . getUInt ( PSTR ( " MaxInterval " ) , 0xFFFF ) ;
2020-08-08 11:17:37 +01:00
// read ReportableChange
2020-09-21 20:49:32 +01:00
JsonParserToken val_attr_rc = attr_config [ PSTR ( " ReportableChange " ) ] ;
if ( val_attr_rc ) {
val_d = val_attr_rc . getFloat ( ) ;
val_str = val_attr_rc . getStr ( ) ;
2020-09-14 21:06:19 +01:00
ZbApplyMultiplier ( val_d , attr . attr_multiplier ) ;
2020-08-08 11:17:37 +01:00
}
// read TimeoutPeriod
2020-09-21 20:49:32 +01:00
uint16_t attr_timeout = attr_config . getUInt ( PSTR ( " TimeoutPeriod " ) , 0x0000 ) ;
2020-08-08 11:17:37 +01:00
2020-09-14 21:06:19 +01:00
bool attr_discrete = Z_isDiscreteDataType ( attr . attr_type ) ;
2020-08-08 11:17:37 +01:00
// all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1
// common bytes
buf . add8 ( attr_direction ? 0x01 : 0x00 ) ;
2020-09-14 21:06:19 +01:00
buf . add16 ( attr . key . id . attr_id ) ;
2020-08-08 11:17:37 +01:00
if ( attr_direction ) {
buf . add16 ( attr_timeout ) ;
2020-06-03 21:39:04 +01:00
} else {
2020-09-14 21:06:19 +01:00
buf . add8 ( attr . attr_type ) ;
2020-08-08 11:17:37 +01:00
buf . add16 ( attr_min_interval ) ;
buf . add16 ( attr_max_interval ) ;
if ( ! attr_discrete ) {
2020-09-14 21:06:19 +01:00
int32_t res = encodeSingleAttribute ( buf , val_d , val_str , attr . attr_type ) ;
2020-08-08 11:17:37 +01:00
if ( res < 0 ) {
2020-09-14 21:06:19 +01:00
Response_P ( PSTR ( " { \" %s \" : \" %s'%s' 0x%02X \" } " ) , XdrvMailbox . command , PSTR ( " Unsupported attribute type " ) , key , attr . attr_type ) ;
2020-08-08 11:17:37 +01:00
return ;
}
}
2020-06-03 21:39:04 +01:00
}
2020-05-29 21:52:45 +01:00
}
}
// did we have any attribute?
if ( 0 = = buf . len ( ) ) {
ResponseCmndChar_P ( PSTR ( " No attribute in list " ) ) ;
return ;
}
// all good, send the packet
2020-10-13 21:45:11 +01:00
packet . transacId = zigbee_devices . getNextSeqNumber ( packet . shortaddr ) ;
packet . msg = buf . getBuffer ( ) ;
packet . len = buf . len ( ) ;
ZigbeeZCLSend_Raw ( packet ) ;
2020-05-29 21:52:45 +01:00
ResponseCmndDone ( ) ;
}
// Parse the "Send" attribute and send the command
2020-09-21 20:49:32 +01:00
void ZbSendSend ( class JsonParserToken val_cmd , uint16_t device , uint16_t groupaddr , uint16_t cluster , uint8_t endpoint , uint16_t manuf ) {
2020-05-29 21:52:45 +01:00
uint8_t cmd = 0 ;
String cmd_str = " " ; // the actual low-level command, either specified or computed
2020-09-21 20:49:32 +01:00
const char * cmd_s = " " ; // pointer to payload string
2020-05-29 21:52:45 +01:00
bool clusterSpecific = true ;
static char delim [ ] = " , " ; // delimiters for parameters
// probe the type of the argument
// If JSON object, it's high level commands
// If String, it's a low level command
2020-09-21 20:49:32 +01:00
if ( val_cmd . isObject ( ) ) {
2020-05-29 21:52:45 +01:00
// we have a high-level command
2020-09-21 20:49:32 +01:00
JsonParserObject cmd_obj = val_cmd . getObject ( ) ;
2020-05-29 21:52:45 +01:00
int32_t cmd_size = cmd_obj . size ( ) ;
if ( cmd_size > 1 ) {
Response_P ( PSTR ( " Only 1 command allowed (%d) " ) , cmd_size ) ;
return ;
} else if ( 1 = = cmd_size ) {
// We have exactly 1 command, parse it
2020-09-21 20:49:32 +01:00
JsonParserKey key = cmd_obj . getFirstElement ( ) ;
JsonParserToken value = key . getValue ( ) ;
2020-05-29 21:52:45 +01:00
uint32_t x = 0 , y = 0 , z = 0 ;
uint16_t cmd_var ;
2020-06-03 21:39:04 +01:00
uint16_t local_cluster_id ;
2020-05-29 21:52:45 +01:00
2020-09-21 20:49:32 +01:00
const __FlashStringHelper * tasmota_cmd = zigbeeFindCommand ( key . getStr ( ) , & local_cluster_id , & cmd_var ) ;
2020-05-29 21:52:45 +01:00
if ( tasmota_cmd ) {
cmd_str = tasmota_cmd ;
} else {
2020-09-21 20:49:32 +01:00
Response_P ( PSTR ( " Unrecognized zigbee command: %s " ) , key . getStr ( ) ) ;
2020-05-29 21:52:45 +01:00
return ;
}
2020-06-03 21:39:04 +01:00
// check cluster
if ( 0xFFFF = = cluster ) {
cluster = local_cluster_id ;
} else if ( cluster ! = local_cluster_id ) {
ResponseCmndChar_P ( PSTR ( " No more than one cluster id per command " ) ) ;
return ;
}
2020-05-29 21:52:45 +01:00
// parse the JSON value, depending on its type fill in x,y,z
2020-09-21 20:49:32 +01:00
if ( value . isNum ( ) ) {
x = value . getUInt ( ) ; // automatic conversion to 0/1
// if (value.is<bool>()) {
// // x = value.as<bool>() ? 1 : 0;
2020-10-10 16:00:37 +01:00
// } else if
2020-09-21 20:49:32 +01:00
// } else if (value.is<unsigned int>()) {
// x = value.as<unsigned int>();
2020-05-29 21:52:45 +01:00
} else {
// if non-bool or non-int, trying char*
2020-09-21 20:49:32 +01:00
const char * s_const = value . getStr ( nullptr ) ;
// const char *s_const = value.as<const char*>();
2020-05-29 21:52:45 +01:00
if ( s_const ! = nullptr ) {
char s [ strlen ( s_const ) + 1 ] ;
strcpy ( s , s_const ) ;
if ( ( nullptr ! = s ) & & ( 0x00 ! = * s ) ) { // ignore any null or empty string, could represent 'null' json value
char * sval = strtok ( s , delim ) ;
if ( sval ) {
x = ZigbeeAliasOrNumber ( sval ) ;
sval = strtok ( nullptr , delim ) ;
if ( sval ) {
y = ZigbeeAliasOrNumber ( sval ) ;
sval = strtok ( nullptr , delim ) ;
if ( sval ) {
z = ZigbeeAliasOrNumber ( sval ) ;
}
}
}
}
}
}
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
if ( 0xFF = = cmd_var ) { // if command number is a variable, replace it with x
cmd = x ;
x = y ; // and shift other variables
y = z ;
} else {
cmd = cmd_var ; // or simply copy the cmd number
}
cmd_str = zigbeeCmdAddParams ( cmd_str . c_str ( ) , x , y , z ) ; // fill in parameters
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
cmd_s = cmd_str . c_str ( ) ;
} else {
// we have zero command, pass through until last error for missing command
}
2020-09-21 20:49:32 +01:00
} else if ( val_cmd . isStr ( ) ) {
2020-05-29 21:52:45 +01:00
// low-level command
// Now parse the string to extract cluster, command, and payload
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
// where AA is the cluster number, BBBB the command number, CCCC... the payload
// First delimiter is '_' for a global command, or '!' for a cluster specific command
2020-09-21 20:49:32 +01:00
const char * data = val_cmd . getStr ( ) ;
2020-06-03 21:39:04 +01:00
uint16_t local_cluster_id = parseHex ( & data , 4 ) ;
// check cluster
if ( 0xFFFF = = cluster ) {
cluster = local_cluster_id ;
} else if ( cluster ! = local_cluster_id ) {
ResponseCmndChar_P ( PSTR ( " No more than one cluster id per command " ) ) ;
return ;
}
2020-05-29 21:52:45 +01:00
// delimiter
if ( ( ' _ ' = = * data ) | | ( ' ! ' = = * data ) ) {
if ( ' _ ' = = * data ) { clusterSpecific = false ; }
data + + ;
} else {
ResponseCmndChar_P ( PSTR ( " Wrong delimiter for payload " ) ) ;
return ;
}
// parse cmd number
cmd = parseHex ( & data , 2 ) ;
// move to end of payload
// delimiter is optional
if ( ' / ' = = * data ) { data + + ; } // skip delimiter
cmd_s = data ;
} else {
// we have an unsupported command type, just ignore it and fallback to missing command
}
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send: \" %s \" " ) ,
device , groupaddr , endpoint , cluster , cmd , cmd_s ) ;
zigbeeZCLSendStr ( device , groupaddr , endpoint , clusterSpecific , manuf , cluster , cmd , cmd_s ) ;
ResponseCmndDone ( ) ;
}
// Parse the "Send" attribute and send the command
2020-10-13 21:45:11 +01:00
void ZbSendRead ( JsonParserToken val_attr , ZigbeeZCLSendMessage & packet ) {
2020-05-29 21:52:45 +01:00
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
// ZbSend {"Device":"0xF289","Endpoint":3,"Read":{"ModelId":true}}
// ZbSend {"Device":"0xF289","Read":{"ModelId":true}}
2020-08-08 11:17:37 +01:00
// ZbSend {"Device":"0xF289","ReadConig":{"Power":true}}
// ZbSend {"Device":"0xF289","Cluster":6,"Endpoint":3,"ReadConfig":0}
2020-05-29 21:52:45 +01:00
// params
size_t attrs_len = 0 ;
uint8_t * attrs = nullptr ; // empty string is valid
2020-08-08 11:17:37 +01:00
size_t attr_item_len = 2 ; // how many bytes per attribute, standard for "Read"
size_t attr_item_offset = 0 ; // how many bytes do we offset to store attribute
2020-10-13 21:45:11 +01:00
if ( ZCL_READ_REPORTING_CONFIGURATION = = packet . cmd ) {
2020-08-08 11:17:37 +01:00
attr_item_len = 3 ;
attr_item_offset = 1 ;
}
2020-05-29 21:52:45 +01:00
2020-09-21 20:49:32 +01:00
if ( val_attr . isArray ( ) ) {
2020-09-13 15:11:20 +01:00
// value is an array []
2020-09-21 20:49:32 +01:00
JsonParserArray attr_arr = val_attr . getArray ( ) ;
2020-08-08 11:17:37 +01:00
attrs_len = attr_arr . size ( ) * attr_item_len ;
attrs = ( uint8_t * ) calloc ( attrs_len , 1 ) ;
2020-05-29 21:52:45 +01:00
uint32_t i = 0 ;
for ( auto value : attr_arr ) {
2020-09-21 20:49:32 +01:00
uint16_t val = value . getUInt ( ) ;
2020-08-08 11:17:37 +01:00
i + = attr_item_offset ;
2020-05-29 21:52:45 +01:00
attrs [ i + + ] = val & 0xFF ;
attrs [ i + + ] = val > > 8 ;
2020-08-08 11:17:37 +01:00
i + = attr_item_len - 2 - attr_item_offset ; // normally 0
2020-05-29 21:52:45 +01:00
}
2020-09-21 20:49:32 +01:00
} else if ( val_attr . isObject ( ) ) {
2020-09-13 15:11:20 +01:00
// value is an object {}
2020-09-21 20:49:32 +01:00
JsonParserObject attr_obj = val_attr . getObject ( ) ;
2020-08-08 11:17:37 +01:00
attrs_len = attr_obj . size ( ) * attr_item_len ;
attrs = ( uint8_t * ) calloc ( attrs_len , 1 ) ;
2020-05-29 21:52:45 +01:00
uint32_t actual_attr_len = 0 ;
// iterate on keys
2020-09-21 20:49:32 +01:00
for ( auto key : attr_obj ) {
JsonParserToken value = key . getValue ( ) ;
2020-05-29 21:52:45 +01:00
bool found = false ;
// scan attributes to find by name, and retrieve type
for ( uint32_t i = 0 ; i < ARRAY_SIZE ( Z_PostProcess ) ; i + + ) {
const Z_AttributeConverter * converter = & Z_PostProcess [ i ] ;
bool match = false ;
uint16_t local_attr_id = pgm_read_word ( & converter - > attribute ) ;
uint16_t local_cluster_id = CxToCluster ( pgm_read_byte ( & converter - > cluster_short ) ) ;
// uint8_t local_type_id = pgm_read_byte(&converter->type);
2020-09-21 20:49:32 +01:00
if ( ( pgm_read_word ( & converter - > name_offset ) ) & & ( 0 = = strcasecmp_P ( key . getStr ( ) , Z_strings + pgm_read_word ( & converter - > name_offset ) ) ) ) {
2020-05-29 21:52:45 +01:00
// match name
// check if there is a conflict with cluster
// TODO
2020-09-21 20:49:32 +01:00
if ( ! ( value . getBool ( ) ) & & attr_item_offset ) {
2020-08-08 11:17:37 +01:00
// If value is false (non-default) then set direction to 1 (for ReadConfig)
attrs [ actual_attr_len ] = 0x01 ;
}
actual_attr_len + = attr_item_offset ;
2020-05-29 21:52:45 +01:00
attrs [ actual_attr_len + + ] = local_attr_id & 0xFF ;
attrs [ actual_attr_len + + ] = local_attr_id > > 8 ;
2020-08-08 11:17:37 +01:00
actual_attr_len + = attr_item_len - 2 - attr_item_offset ; // normally 0
2020-05-29 21:52:45 +01:00
found = true ;
2020-06-03 21:39:04 +01:00
// check cluster
2020-10-13 21:45:11 +01:00
if ( 0xFFFF = = packet . cluster ) {
packet . cluster = local_cluster_id ;
} else if ( packet . cluster ! = local_cluster_id ) {
2020-06-03 21:39:04 +01:00
ResponseCmndChar_P ( PSTR ( " No more than one cluster id per command " ) ) ;
2020-08-08 11:17:37 +01:00
if ( attrs ) { free ( attrs ) ; }
2020-06-03 21:39:04 +01:00
return ;
}
2020-05-29 21:52:45 +01:00
break ; // found, exit loop
}
}
if ( ! found ) {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( " ZIG: Unknown attribute name (ignored): %s " ) , key ) ;
}
}
attrs_len = actual_attr_len ;
} else {
2020-09-13 15:11:20 +01:00
// value is a literal
2020-10-13 21:45:11 +01:00
if ( 0xFFFF ! = packet . cluster ) {
2020-09-21 20:49:32 +01:00
uint16_t val = val_attr . getUInt ( ) ;
2020-09-13 15:11:20 +01:00
attrs_len = attr_item_len ;
attrs = ( uint8_t * ) calloc ( attrs_len , 1 ) ;
attrs [ 0 + attr_item_offset ] = val & 0xFF ; // little endian
attrs [ 1 + attr_item_offset ] = val > > 8 ;
}
2020-05-29 21:52:45 +01:00
}
if ( attrs_len > 0 ) {
2020-10-13 21:45:11 +01:00
// all good, send the packet
packet . transacId = zigbee_devices . getNextSeqNumber ( packet . shortaddr ) ;
packet . msg = attrs ;
packet . len = attrs_len ;
ZigbeeZCLSend_Raw ( packet ) ;
2020-05-29 21:52:45 +01:00
ResponseCmndDone ( ) ;
} else {
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
}
2020-08-08 11:17:37 +01:00
if ( attrs ) { free ( attrs ) ; }
2020-05-29 21:52:45 +01:00
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbSend`
//
2020-05-29 21:52:45 +01:00
// Examples:
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"0006/0000":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Power":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":12.5}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"006/0000%39":12.5}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AnalogInApplicationType":1000000}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"TimeZone":-1000000}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Manufacturer":"Tasmota","ModelId":"Tasmota Z2T Router"}}
2020-02-02 19:53:49 +00:00
void CmndZbSend ( void ) {
2020-03-23 21:46:26 +00:00
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
2020-05-29 21:52:45 +01:00
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
2020-03-23 21:46:26 +00:00
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-09-23 18:38:24 +01:00
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-21 20:49:32 +01:00
if ( ! root ) { ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ; return ; }
2019-11-03 11:41:44 +00:00
// params
2020-05-29 21:52:45 +01:00
uint16_t device = BAD_SHORTADDR ; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t groupaddr = 0x0000 ; // group address valid only if device == BAD_SHORTADDR
uint16_t cluster = 0xFFFF ; // no default
uint8_t endpoint = 0x00 ; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000 ; // Manuf Id in ZCL frame
2019-11-03 11:41:44 +00:00
2020-05-29 21:52:45 +01:00
// parse "Device" and "Group"
2020-09-21 20:49:32 +01:00
JsonParserToken val_device = root [ PSTR ( D_CMND_ZIGBEE_DEVICE ) ] ;
if ( val_device ) {
device = zigbee_devices . parseDeviceParam ( val_device . getStr ( ) ) ;
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = device ) { ResponseCmndChar_P ( PSTR ( " Invalid parameter " ) ) ; return ; }
2020-03-30 18:23:06 +01:00
}
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = device ) { // if not found, check if we have a group
2020-09-21 20:49:32 +01:00
JsonParserToken val_group = root [ PSTR ( D_CMND_ZIGBEE_GROUP ) ] ;
if ( val_group ) {
groupaddr = val_group . getUInt ( ) ;
2020-03-30 18:23:06 +01:00
} else { // no device nor group
ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ;
return ;
2020-03-14 13:17:30 +00:00
}
2020-02-02 19:53:49 +00:00
}
2020-06-03 21:39:04 +01:00
// from here, either device has a device shortaddr, or if BAD_SHORTADDR then use group address
// Note: groupaddr == 0 is valid
2020-02-02 19:53:49 +00:00
2020-05-29 21:52:45 +01:00
// read other parameters
2020-09-21 20:49:32 +01:00
cluster = root . getUInt ( PSTR ( D_CMND_ZIGBEE_CLUSTER ) , cluster ) ;
endpoint = root . getUInt ( PSTR ( D_CMND_ZIGBEE_ENDPOINT ) , endpoint ) ;
manuf = root . getUInt ( PSTR ( D_CMND_ZIGBEE_MANUF ) , manuf ) ;
2019-11-03 11:41:44 +00:00
2020-05-29 21:52:45 +01:00
// infer endpoint
if ( BAD_SHORTADDR = = device ) {
2020-06-03 21:39:04 +01:00
endpoint = 0xFF ; // endpoint not used for group addresses, so use a dummy broadcast endpoint
} else if ( 0 = = endpoint ) { // if it was not already specified, try to guess it
2020-05-29 21:52:45 +01:00
endpoint = zigbee_devices . findFirstEndpoint ( device ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " ZIG: guessing endpoint %d " ) , endpoint ) ;
}
2020-06-03 21:39:04 +01:00
if ( 0 = = endpoint ) { // after this, if it is still zero, then it's an error
ResponseCmndChar_P ( PSTR ( " Missing endpoint " ) ) ;
return ;
}
// from here endpoint is valid and non-zero
// cluster may be already specified or 0xFFFF
2020-03-14 13:17:30 +00:00
2020-09-21 20:49:32 +01:00
JsonParserToken val_cmd = root [ PSTR ( D_CMND_ZIGBEE_SEND ) ] ;
JsonParserToken val_read = root [ PSTR ( D_CMND_ZIGBEE_READ ) ] ;
JsonParserToken val_write = root [ PSTR ( D_CMND_ZIGBEE_WRITE ) ] ;
JsonParserToken val_publish = root [ PSTR ( D_CMND_ZIGBEE_REPORT ) ] ;
JsonParserToken val_response = root [ PSTR ( D_CMND_ZIGBEE_RESPONSE ) ] ;
JsonParserToken val_read_config = root [ PSTR ( D_CMND_ZIGBEE_READ_CONFIG ) ] ;
JsonParserToken val_config = root [ PSTR ( D_CMND_ZIGBEE_CONFIG ) ] ;
uint32_t multi_cmd = ( ( bool ) val_cmd ) + ( ( bool ) val_read ) + ( ( bool ) val_write ) + ( ( bool ) val_publish )
+ ( ( bool ) val_response ) + ( ( bool ) val_read_config ) + ( ( bool ) val_config ) ;
2020-05-29 21:52:45 +01:00
if ( multi_cmd > 1 ) {
2020-08-08 11:17:37 +01:00
ResponseCmndChar_P ( PSTR ( " Can only have one of: 'Send', 'Read', 'Write', 'Report', 'Reponse', 'ReadConfig' or 'Config' " ) ) ;
2020-05-29 21:52:45 +01:00
return ;
}
2020-06-03 21:39:04 +01:00
// from here we have one and only one command
2020-03-14 13:17:30 +00:00
2020-10-13 21:45:11 +01:00
// collate information in a ready to send packet
ZigbeeZCLSendMessage packet ( {
device ,
groupaddr ,
cluster /*cluster*/ ,
endpoint ,
ZCL_READ_ATTRIBUTES ,
manuf , /* manuf */
false /* not cluster specific */ ,
false /* no response */ ,
2020-10-19 19:34:40 +01:00
false /* discover route */ ,
2020-10-13 21:45:11 +01:00
0 , /* zcl transaction id */
nullptr , 0
} ) ;
2020-09-21 20:49:32 +01:00
if ( val_cmd ) {
2020-05-29 21:52:45 +01:00
// "Send":{...commands...}
2020-06-03 21:39:04 +01:00
// we accept either a string or a JSON object
2020-05-29 21:52:45 +01:00
ZbSendSend ( val_cmd , device , groupaddr , cluster , endpoint , manuf ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_read ) {
2020-05-29 21:52:45 +01:00
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
2020-06-03 21:39:04 +01:00
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_READ_ATTRIBUTES ;
ZbSendRead ( val_read , packet ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_write ) {
2020-06-03 21:39:04 +01:00
// only KSON object
2020-09-21 20:49:32 +01:00
if ( ! val_write . isObject ( ) ) {
2020-05-29 21:52:45 +01:00
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
return ;
2019-11-03 11:41:44 +00:00
}
2020-05-29 21:52:45 +01:00
// "Write":{...attributes...}
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_WRITE_ATTRIBUTES ;
ZbSendReportWrite ( val_write , packet ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_publish ) {
2020-08-08 11:17:37 +01:00
// "Publish":{...attributes...}
2020-06-03 21:39:04 +01:00
// only KSON object
2020-09-21 20:49:32 +01:00
if ( ! val_publish . isObject ( ) ) {
2020-05-29 21:52:45 +01:00
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
return ;
}
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_REPORT_ATTRIBUTES ;
ZbSendReportWrite ( val_publish , packet ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_response ) {
2020-05-29 21:52:45 +01:00
// "Report":{...attributes...}
2020-06-03 21:39:04 +01:00
// only KSON object
2020-09-21 20:49:32 +01:00
if ( ! val_response . isObject ( ) ) {
2020-06-03 21:39:04 +01:00
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
return ;
}
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_READ_ATTRIBUTES_RESPONSE ;
ZbSendReportWrite ( val_response , packet ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_read_config ) {
2020-08-08 11:17:37 +01:00
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_READ_REPORTING_CONFIGURATION ;
ZbSendRead ( val_read_config , packet ) ;
2020-09-21 20:49:32 +01:00
} else if ( val_config ) {
2020-08-08 11:17:37 +01:00
// "Config":{...attributes...}
// only JSON object
2020-09-21 20:49:32 +01:00
if ( ! val_config . isObject ( ) ) {
2020-08-08 11:17:37 +01:00
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
return ;
}
2020-10-13 21:45:11 +01:00
packet . cmd = ZCL_CONFIGURE_REPORTING ;
ZbSendReportWrite ( val_config , packet ) ;
2019-11-03 11:41:44 +00:00
} else {
2020-06-03 21:39:04 +01:00
Response_P ( PSTR ( " Missing zigbee 'Send', 'Write', 'Report' or 'Response' " ) ) ;
2019-11-03 11:41:44 +00:00
return ;
}
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbBind`
//
2020-03-26 19:58:59 +00:00
void ZbBindUnbind ( bool unbind ) { // false = bind, true = unbind
2020-03-23 21:46:26 +00:00
// ZbBind {"Device":"<device>", "Endpoint":<endpoint>, "Cluster":<cluster>, "ToDevice":"<to_device>", "ToEndpoint":<to_endpoint>, "ToGroup":<to_group> }
2020-03-26 19:58:59 +00:00
// ZbUnbind {"Device":"<device>", "Endpoint":<endpoint>, "Cluster":<cluster>, "ToDevice":"<to_device>", "ToEndpoint":<to_endpoint>, "ToGroup":<to_group> }
2020-02-02 19:53:49 +00:00
// local endpoint is always 1, IEEE addresses are calculated
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-09-23 18:38:24 +01:00
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-21 20:49:32 +01:00
if ( ! root ) { ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ; return ; }
2020-02-02 19:53:49 +00:00
// params
2020-09-21 20:49:32 +01:00
uint16_t srcDevice = BAD_SHORTADDR ; // BAD_SHORTADDR is broadcast, so considered invalid
2020-05-17 17:33:42 +01:00
uint16_t dstDevice = BAD_SHORTADDR ; // BAD_SHORTADDR is broadcast, so considered invalid
2020-03-01 10:25:59 +00:00
uint64_t dstLongAddr = 0 ;
uint8_t endpoint = 0x00 ; // 0x00 is invalid for the src endpoint
2020-09-21 20:49:32 +01:00
uint8_t toendpoint = 0x01 ; // default dest endpoint to 0x01
2020-03-01 10:25:59 +00:00
uint16_t toGroup = 0x0000 ; // group address
2020-02-02 19:53:49 +00:00
uint16_t cluster = 0 ; // 0xFFFF is invalid
uint32_t group = 0xFFFFFFFF ; // 16 bits values, otherwise 0xFFFFFFFF is unspecified
2020-03-01 10:25:59 +00:00
// Information about source device: "Device", "Endpoint", "Cluster"
// - the source endpoint must have a known IEEE address
2020-09-21 20:49:32 +01:00
srcDevice = zigbee_devices . parseDeviceParam ( root . getStr ( PSTR ( D_CMND_ZIGBEE_DEVICE ) , nullptr ) ) ;
if ( BAD_SHORTADDR = = srcDevice ) { ResponseCmndChar_P ( PSTR ( " Unknown source device " ) ) ; return ; }
2020-03-01 10:25:59 +00:00
// check if IEEE address is known
uint64_t srcLongAddr = zigbee_devices . getDeviceLongAddr ( srcDevice ) ;
2020-03-16 17:55:58 +00:00
if ( 0 = = srcLongAddr ) { ResponseCmndChar_P ( PSTR ( " Unknown source IEEE address " ) ) ; return ; }
2020-03-01 10:25:59 +00:00
// look for source endpoint
2020-09-21 20:49:32 +01:00
endpoint = root . getUInt ( PSTR ( D_CMND_ZIGBEE_ENDPOINT ) , endpoint ) ;
if ( 0 = = endpoint ) { endpoint = zigbee_devices . findFirstEndpoint ( srcDevice ) ; }
2020-03-01 10:25:59 +00:00
// look for source cluster
2020-09-21 20:49:32 +01:00
JsonParserToken val_cluster = root [ PSTR ( D_CMND_ZIGBEE_CLUSTER ) ] ;
if ( val_cluster ) {
cluster = val_cluster . getUInt ( cluster ) ; // first convert as number
2020-08-08 11:17:37 +01:00
if ( 0 = = cluster ) {
2020-09-21 20:49:32 +01:00
zigbeeFindAttributeByName ( val_cluster . getStr ( ) , & cluster , nullptr , nullptr ) ;
2020-08-08 11:17:37 +01:00
}
}
// Or Group Address - we don't need a dstEndpoint in this case
2020-09-21 20:49:32 +01:00
JsonParserToken to_group = root [ PSTR ( " ToGroup " ) ] ;
if ( to_group ) { toGroup = to_group . getUInt ( toGroup ) ; }
2020-02-02 19:53:49 +00:00
2020-03-01 10:25:59 +00:00
// Either Device address
// In this case the following parameters are mandatory
// - "ToDevice" and the device must have a known IEEE address
// - "ToEndpoint"
2020-09-21 20:49:32 +01:00
JsonParserToken dst_device = root [ PSTR ( " ToDevice " ) ] ;
2020-08-08 11:17:37 +01:00
// If no target is specified, we default to coordinator 0x0000
2020-09-21 20:49:32 +01:00
if ( ( ! to_group ) & & ( ! dst_device ) ) {
2020-08-08 11:17:37 +01:00
dstDevice = 0x0000 ;
}
2020-09-21 20:49:32 +01:00
if ( ( dst_device ) | | ( BAD_SHORTADDR ! = dstDevice ) ) {
2020-08-08 11:17:37 +01:00
if ( BAD_SHORTADDR = = dstDevice ) {
2020-09-21 20:49:32 +01:00
dstDevice = zigbee_devices . parseDeviceParam ( dst_device . getStr ( nullptr ) ) ;
2020-08-08 11:17:37 +01:00
if ( BAD_SHORTADDR = = dstDevice ) { ResponseCmndChar_P ( PSTR ( " Invalid parameter " ) ) ; return ; }
}
2020-03-01 10:25:59 +00:00
if ( 0x0000 = = dstDevice ) {
dstLongAddr = localIEEEAddr ;
} else {
dstLongAddr = zigbee_devices . getDeviceLongAddr ( dstDevice ) ;
}
2020-03-16 17:55:58 +00:00
if ( 0 = = dstLongAddr ) { ResponseCmndChar_P ( PSTR ( " Unknown dest IEEE address " ) ) ; return ; }
2020-03-01 10:25:59 +00:00
2020-09-21 20:49:32 +01:00
toendpoint = root . getUInt ( PSTR ( " ToEndpoint " ) , toendpoint ) ;
2020-03-01 10:25:59 +00:00
}
// make sure we don't have conflicting parameters
2020-09-21 20:49:32 +01:00
if ( to_group & & dstLongAddr ) { ResponseCmndChar_P ( PSTR ( " Cannot have both \" ToDevice \" and \" ToGroup \" " ) ) ; return ; }
if ( ! to_group & & ! dstLongAddr ) { ResponseCmndChar_P ( PSTR ( " Missing \" ToDevice \" or \" ToGroup \" " ) ) ; return ; }
2020-02-02 19:53:49 +00:00
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
2020-03-14 13:17:30 +00:00
SBuffer buf ( 34 ) ;
2020-02-02 19:53:49 +00:00
buf . add8 ( Z_SREQ | Z_ZDO ) ;
2020-03-26 19:58:59 +00:00
if ( unbind ) {
buf . add8 ( ZDO_UNBIND_REQ ) ;
} else {
buf . add8 ( ZDO_BIND_REQ ) ;
}
2020-03-01 10:25:59 +00:00
buf . add16 ( srcDevice ) ;
buf . add64 ( srcLongAddr ) ;
2020-02-02 19:53:49 +00:00
buf . add8 ( endpoint ) ;
buf . add16 ( cluster ) ;
2020-03-01 10:25:59 +00:00
if ( dstLongAddr ) {
buf . add8 ( Z_Addr_IEEEAddress ) ; // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf . add64 ( dstLongAddr ) ;
buf . add8 ( toendpoint ) ;
} else {
buf . add8 ( Z_Addr_Group ) ; // DstAddrMode - 0x01 = GROUP_ADDRESS
buf . add16 ( toGroup ) ;
}
2020-02-02 19:53:49 +00:00
ZigbeeZNPSend ( buf . getBuffer ( ) , buf . len ( ) ) ;
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_ZNP
2020-02-02 19:53:49 +00:00
2020-07-05 20:01:26 +01:00
# ifdef USE_ZIGBEE_EZSP
SBuffer buf ( 24 ) ;
2020-07-20 16:24:51 +01:00
2020-07-05 20:01:26 +01:00
// ZDO message payload (see Zigbee spec 2.4.3.2.2)
buf . add64 ( srcLongAddr ) ;
buf . add8 ( endpoint ) ;
buf . add16 ( cluster ) ;
if ( dstLongAddr ) {
buf . add8 ( Z_Addr_IEEEAddress ) ; // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf . add64 ( dstLongAddr ) ;
buf . add8 ( toendpoint ) ;
} else {
buf . add8 ( Z_Addr_Group ) ; // DstAddrMode - 0x01 = GROUP_ADDRESS
buf . add16 ( toGroup ) ;
}
EZ_SendZDO ( srcDevice , unbind ? ZDO_UNBIND_REQ : ZDO_BIND_REQ , buf . buf ( ) , buf . len ( ) ) ;
# endif // USE_ZIGBEE_EZSP
2020-02-02 19:53:49 +00:00
ResponseCmndDone ( ) ;
}
2020-03-26 19:58:59 +00:00
//
// Command ZbBind
//
void CmndZbBind ( void ) {
ZbBindUnbind ( false ) ;
}
//
// Command ZbBind
//
void CmndZbUnbind ( void ) {
ZbBindUnbind ( true ) ;
}
2020-03-30 18:23:06 +01:00
//
// Command `ZbBindState`
2020-07-29 09:02:04 +01:00
// `ZbBindState<x>` as index if it does not fit. If default, `1` starts at the beginning
2020-03-30 18:23:06 +01:00
//
void CmndZbBindState ( void ) {
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data ) ;
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-07-29 09:02:04 +01:00
uint8_t index = XdrvMailbox . index - 1 ; // change default 1 to 0
2020-03-30 18:23:06 +01:00
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
2020-03-30 18:23:06 +01:00
SBuffer buf ( 10 ) ;
buf . add8 ( Z_SREQ | Z_ZDO ) ; // 25
buf . add8 ( ZDO_MGMT_BIND_REQ ) ; // 33
buf . add16 ( shortaddr ) ; // shortaddr
2020-07-29 09:02:04 +01:00
buf . add8 ( index ) ; // StartIndex = 0
2020-03-30 18:23:06 +01:00
ZigbeeZNPSend ( buf . getBuffer ( ) , buf . len ( ) ) ;
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_ZNP
2020-03-30 18:23:06 +01:00
2020-07-05 20:01:26 +01:00
# ifdef USE_ZIGBEE_EZSP
// ZDO message payload (see Zigbee spec 2.4.3.3.4)
2020-07-29 09:02:04 +01:00
uint8_t buf [ ] = { index } ; // index = 0
2020-07-05 20:01:26 +01:00
EZ_SendZDO ( shortaddr , ZDO_Mgmt_Bind_req , buf , sizeof ( buf ) ) ;
# endif // USE_ZIGBEE_EZSP
2020-03-30 18:23:06 +01:00
ResponseCmndDone ( ) ;
}
2019-10-13 11:56:52 +01:00
// Probe a specific device to get its endpoints and supported clusters
2020-02-02 19:53:49 +00:00
void CmndZbProbe ( void ) {
2020-02-23 15:46:00 +00:00
CmndZbProbeOrPing ( true ) ;
}
2020-03-23 21:46:26 +00:00
//
// Common code for `ZbProbe` and `ZbPing`
//
2020-02-23 15:46:00 +00:00
void CmndZbProbeOrPing ( boolean probe ) {
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-01-17 23:02:01 +00:00
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data ) ;
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2019-10-13 11:56:52 +01:00
// everything is good, we can send the command
2020-02-23 15:46:00 +00:00
Z_SendIEEEAddrReq ( shortaddr ) ;
if ( probe ) {
Z_SendActiveEpReq ( shortaddr ) ;
}
2019-10-13 11:56:52 +01:00
ResponseCmndDone ( ) ;
2019-08-31 20:23:32 +01:00
}
2020-02-23 15:46:00 +00:00
// Ping a device, actually a simplified version of ZbProbe
void CmndZbPing ( void ) {
CmndZbProbeOrPing ( false ) ;
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbName`
2020-01-17 23:02:01 +00:00
// Specify, read or erase a Friendly Name
2020-03-23 21:46:26 +00:00
//
2020-02-02 19:53:49 +00:00
void CmndZbName ( void ) {
2020-01-17 23:02:01 +00:00
// Syntax is:
2020-03-23 21:46:26 +00:00
// ZbName <device_id>,<friendlyname> - assign a friendly name
// ZbName <device_id> - display the current friendly name
// ZbName <device_id>, - remove friendly name
2020-01-17 23:02:01 +00:00
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-01-17 23:02:01 +00:00
// check if parameters contain a comma ','
char * p ;
char * str = strtok_r ( XdrvMailbox . data , " , " , & p ) ;
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data , true ) ; // in case of short_addr, it must be already registered
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-01-17 23:02:01 +00:00
if ( p = = nullptr ) {
2020-03-14 13:17:30 +00:00
const char * friendlyName = zigbee_devices . getFriendlyName ( shortaddr ) ;
Response_P ( PSTR ( " { \" 0x%04X \" :{ \" " D_JSON_ZIGBEE_NAME " \" : \" %s \" }} " ) , shortaddr , friendlyName ? friendlyName : " " ) ;
2020-01-17 23:02:01 +00:00
} else {
2020-08-23 14:22:36 +01:00
if ( strlen ( p ) > 32 ) { p [ 32 ] = 0x00 ; } // truncate to 32 chars max
2020-01-17 23:02:01 +00:00
zigbee_devices . setFriendlyName ( shortaddr , p ) ;
2020-01-19 21:59:02 +00:00
Response_P ( PSTR ( " { \" 0x%04X \" :{ \" " D_JSON_ZIGBEE_NAME " \" : \" %s \" }} " ) , shortaddr , p ) ;
2020-01-17 23:02:01 +00:00
}
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbName`
2020-03-01 10:25:59 +00:00
// Specify, read or erase a ModelId, only for debug purposes
2020-03-23 21:46:26 +00:00
//
2020-03-01 10:25:59 +00:00
void CmndZbModelId ( void ) {
// Syntax is:
2020-03-23 21:46:26 +00:00
// ZbName <device_id>,<friendlyname> - assign a friendly name
// ZbName <device_id> - display the current friendly name
// ZbName <device_id>, - remove friendly name
2020-03-01 10:25:59 +00:00
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-03-01 10:25:59 +00:00
// check if parameters contain a comma ','
char * p ;
char * str = strtok_r ( XdrvMailbox . data , " , " , & p ) ;
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data , true ) ; // in case of short_addr, it must be already registered
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-03-01 10:25:59 +00:00
if ( p = = nullptr ) {
2020-03-14 13:17:30 +00:00
const char * modelId = zigbee_devices . getModelId ( shortaddr ) ;
Response_P ( PSTR ( " { \" 0x%04X \" :{ \" " D_JSON_ZIGBEE_MODELID " \" : \" %s \" }} " ) , shortaddr , modelId ? modelId : " " ) ;
2020-03-01 10:25:59 +00:00
} else {
zigbee_devices . setModelId ( shortaddr , p ) ;
Response_P ( PSTR ( " { \" 0x%04X \" :{ \" " D_JSON_ZIGBEE_MODELID " \" : \" %s \" }} " ) , shortaddr , p ) ;
}
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbLight`
2020-03-14 13:17:30 +00:00
// Specify, read or erase a Light type for Hue/Alexa integration
void CmndZbLight ( void ) {
// Syntax is:
// ZbLight <device_id>,<x> - assign a bulb type 0-5
// ZbLight <device_id> - display the current bulb type and status
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-03-14 13:17:30 +00:00
// check if parameters contain a comma ','
char * p ;
char * str = strtok_r ( XdrvMailbox . data , " , " , & p ) ;
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data , true ) ; // in case of short_addr, it must be already registered
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-03-14 13:17:30 +00:00
if ( p ) {
int8_t bulbtype = strtol ( p , nullptr , 10 ) ;
2020-03-23 07:25:01 +00:00
if ( bulbtype > 5 ) { bulbtype = 5 ; }
if ( bulbtype < - 1 ) { bulbtype = - 1 ; }
2020-10-07 19:04:33 +01:00
zigbee_devices . setLightProfile ( shortaddr , bulbtype ) ;
2020-03-14 13:17:30 +00:00
}
String dump = zigbee_devices . dumpLightState ( shortaddr ) ;
Response_P ( PSTR ( " { \" " D_PRFX_ZB D_CMND_ZIGBEE_LIGHT " \" :%s} " ) , dump . c_str ( ) ) ;
2020-07-20 16:24:51 +01:00
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_STAT , PSTR ( D_PRFX_ZB D_CMND_ZIGBEE_LIGHT ) ) ;
2020-03-14 13:17:30 +00:00
ResponseCmndDone ( ) ;
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbForget`
2020-01-17 23:02:01 +00:00
// Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices
2020-03-23 21:46:26 +00:00
//
2020-02-02 19:53:49 +00:00
void CmndZbForget ( void ) {
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-01-17 23:02:01 +00:00
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data ) ;
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-01-17 23:02:01 +00:00
// everything is good, we can send the command
if ( zigbee_devices . removeDevice ( shortaddr ) ) {
ResponseCmndDone ( ) ;
} else {
2020-03-16 17:55:58 +00:00
ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ;
2020-01-17 23:02:01 +00:00
}
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbSave`
2020-01-17 23:02:01 +00:00
// Save Zigbee information to flash
2020-03-23 21:46:26 +00:00
//
2020-02-02 19:53:49 +00:00
void CmndZbSave ( void ) {
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-01-17 23:02:01 +00:00
saveZigbeeDevices ( ) ;
ResponseCmndDone ( ) ;
}
2020-03-22 15:11:01 +00:00
// Restore a device configuration previously exported via `ZbStatus2``
// Format:
// Either the entire `ZbStatus3` export, or an array or just the device configuration.
// If array, if can contain multiple devices
// ZbRestore {"ZbStatus3":[{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}]}
// ZbRestore [{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}]
// ZbRestore {"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
void CmndZbRestore ( void ) {
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-09-23 18:38:24 +01:00
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserToken root = parser . getRoot ( ) ;
2020-09-21 20:49:32 +01:00
2020-09-23 18:52:34 +01:00
if ( ! parser | | ! ( root . isObject ( ) | | root . isArray ( ) ) ) { ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ; return ; }
2020-03-22 15:11:01 +00:00
// Check is root contains `ZbStatus<x>` key, if so change the root
2020-09-21 20:49:32 +01:00
JsonParserToken zbstatus = root . getObject ( ) . findStartsWith ( PSTR ( " ZbStatus " ) ) ;
if ( zbstatus ) {
root = zbstatus ;
2020-03-22 15:11:01 +00:00
}
// check if the root is an array
2020-09-21 20:49:32 +01:00
if ( root . isArray ( ) ) {
JsonParserArray arr = JsonParserArray ( root ) ;
for ( const auto elt : arr ) {
2020-03-22 15:11:01 +00:00
// call restore on each item
2020-09-21 20:49:32 +01:00
if ( elt . isObject ( ) ) {
int32_t res = zigbee_devices . deviceRestore ( JsonParserObject ( elt ) ) ;
if ( res < 0 ) {
ResponseCmndChar_P ( PSTR ( " Restore failed " ) ) ;
return ;
}
2020-03-22 15:11:01 +00:00
}
}
2020-09-21 20:49:32 +01:00
} else if ( root . isObject ( ) ) {
int32_t res = zigbee_devices . deviceRestore ( JsonParserObject ( root ) ) ;
2020-03-22 15:11:01 +00:00
if ( res < 0 ) {
ResponseCmndChar_P ( PSTR ( " Restore failed " ) ) ;
return ;
}
// call restore on a single object
} else {
ResponseCmndChar_P ( PSTR ( " Missing parameters " ) ) ;
return ;
}
ResponseCmndDone ( ) ;
}
2020-03-23 21:46:26 +00:00
//
// Command `ZbPermitJoin`
2019-09-29 14:38:26 +01:00
// Allow or Deny pairing of new Zigbee devices
2020-03-23 21:46:26 +00:00
//
2020-03-14 13:17:30 +00:00
void CmndZbPermitJoin ( void ) {
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-06-29 21:21:32 +01:00
2019-09-15 10:10:59 +01:00
uint32_t payload = XdrvMailbox . payload ;
2020-03-14 13:17:30 +00:00
uint8_t duration = 60 ; // default 60s
2019-09-15 10:10:59 +01:00
2020-03-14 13:17:30 +00:00
if ( payload < = 0 ) {
duration = 0 ;
2019-09-15 10:10:59 +01:00
}
2020-03-14 13:17:30 +00:00
2020-06-29 21:21:32 +01:00
// ZNP Version
2020-06-16 19:01:14 +01:00
# ifdef USE_ZIGBEE_ZNP
2020-07-20 18:30:32 +01:00
if ( 99 = = payload ) {
duration = 0xFF ; // unlimited time
}
2020-06-29 21:21:32 +01:00
uint16_t dstAddr = 0xFFFC ; // default addr
2020-03-14 13:17:30 +00:00
SBuffer buf ( 34 ) ;
buf . add8 ( Z_SREQ | Z_ZDO ) ; // 25
buf . add8 ( ZDO_MGMT_PERMIT_JOIN_REQ ) ; // 36
buf . add8 ( 0x0F ) ; // AddrMode
buf . add16 ( 0xFFFC ) ; // DstAddr
buf . add8 ( duration ) ;
buf . add8 ( 0x00 ) ; // TCSignificance
ZigbeeZNPSend ( buf . getBuffer ( ) , buf . len ( ) ) ;
2020-06-29 21:21:32 +01:00
2020-06-16 19:01:14 +01:00
# endif // USE_ZIGBEE_ZNP
2020-03-14 13:17:30 +00:00
2020-06-29 21:21:32 +01:00
// EZSP VERSION
# ifdef USE_ZIGBEE_EZSP
2020-07-20 18:30:32 +01:00
if ( 99 = = payload ) {
ResponseCmndChar_P ( PSTR ( " Unlimited time not supported " ) ) ; return ;
}
2020-06-29 21:21:32 +01:00
SBuffer buf ( 3 ) ;
buf . add16 ( EZSP_permitJoining ) ;
buf . add8 ( duration ) ;
2020-07-22 18:29:16 +01:00
ZigbeeEZSPSendCmd ( buf . getBuffer ( ) , buf . len ( ) ) ;
2020-07-20 18:30:32 +01:00
// send ZDO_Mgmt_Permit_Joining_req to all routers
buf . setLen ( 0 ) ;
buf . add8 ( duration ) ;
buf . add8 ( 0x01 ) ; // TC_Significance - This field shall always have a value of 1, indicating a request to change the Trust Center policy. If a frame is received with a value of 0, it shall be treated as having a value of 1.
2020-07-22 18:29:16 +01:00
EZ_SendZDO ( 0xFFFC , ZDO_Mgmt_Permit_Joining_req , buf . buf ( ) , buf . len ( ) ) ;
2020-09-24 18:15:07 +01:00
// Set Timer after the end of the period, and reset a non-expired previous timer
if ( duration > 0 ) {
// Log pairing mode enabled
Response_P ( PSTR ( " { \" " D_JSON_ZIGBEE_STATE " \" :{ \" Status \" :21, \" Message \" : \" Pairing mode enabled \" }} " ) ) ;
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_ZIGBEE_STATE ) ) ;
2020-10-19 19:34:40 +01:00
zigbee . permit_end_time = millis ( ) + duration * 1000 ;
AddLog_P2 ( LOG_LEVEL_INFO , " zigbee.permit_end_time = %d " , zigbee . permit_end_time ) ;
} else {
zigbee . permit_end_time = millis ( ) ;
2020-09-24 18:15:07 +01:00
}
2020-06-29 21:21:32 +01:00
# endif // USE_ZIGBEE_EZSP
2019-09-15 10:10:59 +01:00
ResponseCmndDone ( ) ;
}
2020-07-10 19:15:12 +01:00
# ifdef USE_ZIGBEE_EZSP
//
// `ZbListen`: add a multicast group to listen to
// Overcomes a current limitation that EZSP only shows messages from multicast groups it listens too
//
// Ex: `ZbListen 99`, `ZbListen2 100`
void CmndZbEZSPListen ( void ) {
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-08-05 19:49:07 +01:00
int32_t index = XdrvMailbox . index ; // 0 is reserved for group 0 (auto-config)
2020-07-10 19:15:12 +01:00
int32_t group = XdrvMailbox . payload ;
if ( group < = 0 ) {
group = 0 ;
} else if ( group > 0xFFFF ) {
group = 0xFFFF ;
}
2020-07-20 16:24:51 +01:00
2020-07-10 19:15:12 +01:00
SBuffer buf ( 8 ) ;
buf . add16 ( EZSP_setMulticastTableEntry ) ;
buf . add8 ( index ) ;
buf . add16 ( group ) ; // group
buf . add8 ( 0x01 ) ; // endpoint
buf . add8 ( 0x00 ) ; // network index
2020-07-22 18:29:16 +01:00
ZigbeeEZSPSendCmd ( buf . getBuffer ( ) , buf . len ( ) ) ;
2020-07-10 19:15:12 +01:00
ResponseCmndDone ( ) ;
}
2020-10-19 19:34:40 +01:00
void ZigbeeGlowPermitJoinLight ( void ) {
static const uint16_t cycle_time = 1000 ; // cycle up and down in 1000 ms
static const uint16_t half_cycle_time = cycle_time / 2 ; // cycle up and down in 1000 ms
if ( zigbee . permit_end_time ) {
uint16_t led_power = 0 ; // turn led off
// permit join is ongoing
if ( TimeReached ( zigbee . permit_end_time ) ) {
zigbee . permit_end_time = 0 ; // disable timer
Z_PermitJoinDisable ( ) ;
} else {
uint32_t millis_to_go = millis ( ) - zigbee . permit_end_time ;
uint32_t sub_second = millis_to_go % cycle_time ;
if ( sub_second < = half_cycle_time ) {
led_power = changeUIntScale ( sub_second , 0 , half_cycle_time , 0 , 1023 ) ;
} else {
led_power = changeUIntScale ( sub_second , half_cycle_time , cycle_time , 1023 , 0 ) ;
}
led_power = ledGamma10_10 ( led_power ) ;
}
// change the led state
uint32_t led_pin = Pin ( GPIO_LEDLNK ) ;
if ( led_pin < 99 ) {
analogWrite ( led_pin , ledlnk_inverted ? 1023 - led_power : led_power ) ;
}
}
}
2020-07-10 19:15:12 +01:00
# endif // USE_ZIGBEE_EZSP
2020-03-23 21:46:26 +00:00
//
// Command `ZbStatus`
//
2020-02-02 19:53:49 +00:00
void CmndZbStatus ( void ) {
2020-01-17 23:02:01 +00:00
if ( ZigbeeSerial ) {
2020-03-16 17:55:58 +00:00
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-01-17 23:02:01 +00:00
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data ) ;
2020-10-07 19:04:33 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2020-05-17 17:33:42 +01:00
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
2020-01-17 23:02:01 +00:00
}
2020-04-22 15:07:52 +01:00
2020-01-17 23:02:01 +00:00
String dump = zigbee_devices . dump ( XdrvMailbox . index , shortaddr ) ;
Response_P ( PSTR ( " { \" %s%d \" :%s} " ) , XdrvMailbox . command , XdrvMailbox . index , dump . c_str ( ) ) ;
}
}
2020-10-07 19:04:33 +01:00
//
// Innder part of ZbData parsing
//
2020-10-09 18:10:36 +01:00
// {"L02":{"Dimmer":10,"Sat":254}}
2020-10-07 19:04:33 +01:00
bool parseDeviceInnerData ( class Z_Device & device , JsonParserObject root ) {
for ( auto data_elt : root ) {
2020-10-09 18:10:36 +01:00
// Parse key in format "L02":....
2020-10-07 19:04:33 +01:00
const char * data_type_str = data_elt . getStr ( ) ;
Z_Data_Type data_type ;
switch ( data_type_str [ 0 ] ) {
case ' P ' : data_type = Z_Data_Type : : Z_Plug ; break ;
case ' L ' : data_type = Z_Data_Type : : Z_Light ; break ;
case ' O ' : data_type = Z_Data_Type : : Z_OnOff ; break ;
case ' T ' : data_type = Z_Data_Type : : Z_Thermo ; break ;
case ' A ' : data_type = Z_Data_Type : : Z_Alarm ; break ;
case ' _ ' : data_type = Z_Data_Type : : Z_Device ; break ;
default : data_type = Z_Data_Type : : Z_Unknown ; break ;
}
// The format should be a valid Code Lette followed by '-'
if ( data_type = = Z_Data_Type : : Z_Unknown ) {
Response_P ( PSTR ( " { \" %s \" : \" %s \" %s \" \" } " ) , XdrvMailbox . command , PSTR ( " Invalid Parameters " ) , data_type_str ) ;
return false ;
}
JsonParserObject data_values = data_elt . getValue ( ) . getObject ( ) ;
if ( ! data_values ) { return false ; }
// Decode the endpoint number
uint8_t endpoint = strtoul ( & data_type_str [ 1 ] , nullptr , 16 ) ; // hex base 16
JsonParserToken val ;
2020-10-09 18:10:36 +01:00
// Import generic attributes first
Z_Data & data = device . data . getByType ( data_type , endpoint ) ;
2020-10-10 16:00:37 +01:00
2020-10-09 18:10:36 +01:00
// scan through attributes
for ( auto attr : data_values ) {
JsonParserToken attr_value = attr . getValue ( ) ;
uint8_t conv_zigbee_type ;
Z_Data_Type conv_data_type ;
2020-10-10 16:00:37 +01:00
uint8_t conv_map_offset ;
2020-10-09 18:10:36 +01:00
if ( zigbeeFindAttributeByName ( attr . getStr ( ) , nullptr , nullptr , nullptr , & conv_zigbee_type , & conv_data_type , & conv_map_offset ) ! = nullptr ) {
// found an attribute matching the name, does is fit the type?
if ( conv_data_type = = data_type ) {
// we got a match. Bear in mind that a zero value is not a valid 'data_type'
uint8_t * attr_address = ( ( uint8_t * ) & data ) + sizeof ( Z_Data ) + conv_map_offset ;
uint32_t uval32 = attr_value . getUInt ( ) ; // call converter to uint only once
int32_t ival32 = attr_value . getInt ( ) ; // call converter to int only once
switch ( conv_zigbee_type ) {
case Zenum8 :
case Zuint8 : * ( uint8_t * ) attr_address = uval32 ; break ;
case Zenum16 :
case Zuint16 : * ( uint16_t * ) attr_address = uval32 ; break ;
case Zuint32 : * ( uint32_t * ) attr_address = uval32 ; break ;
case Zint8 : * ( int8_t * ) attr_address = ival32 ; break ;
case Zint16 : * ( int16_t * ) attr_address = ival32 ; break ;
case Zint32 : * ( int32_t * ) attr_address = ival32 ; break ;
}
} else if ( conv_data_type ! = Z_Data_Type : : Z_Unknown ) {
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_ZIGBEE " attribute %s is wrong type %d (expected %d) " ) , attr . getStr ( ) , ( uint8_t ) data_type , ( uint8_t ) conv_data_type ) ;
}
2020-10-07 19:04:33 +01:00
}
2020-10-09 18:10:36 +01:00
}
// Import specific attributes that are not handled with the generic method
switch ( data_type ) {
// case Z_Data_Type::Z_Plug:
// {
// Z_Data_Plug & plug = (Z_Data_Plug&) data;
// }
// break;
// case Z_Data_Type::Z_Light:
// {
// Z_Data_Light & light = (Z_Data_Light&) data;
// }
// break;
2020-10-07 19:04:33 +01:00
case Z_Data_Type : : Z_OnOff :
{
2020-10-09 18:10:36 +01:00
Z_Data_OnOff & onoff = ( Z_Data_OnOff & ) data ;
2020-10-07 19:04:33 +01:00
if ( val = data_values [ PSTR ( " Power " ) ] ) { onoff . setPower ( val . getUInt ( ) ? true : false ) ; }
}
break ;
2020-10-09 18:10:36 +01:00
// case Z_Data_Type::Z_Thermo:
// {
// Z_Data_Thermo & thermo = (Z_Data_Thermo&) data;
// }
// break;
// case Z_Data_Type::Z_Alarm:
// {
// Z_Data_Alarm & alarm = (Z_Data_Alarm&) data;
// }
// break;
2020-10-07 19:04:33 +01:00
case Z_Data_Type : : Z_Device :
{
if ( val = data_values [ PSTR ( D_CMND_ZIGBEE_LINKQUALITY ) ] ) { device . lqi = val . getUInt ( ) ; }
if ( val = data_values [ PSTR ( " BatteryPercentage " ) ] ) { device . batterypercent = val . getUInt ( ) ; }
if ( val = data_values [ PSTR ( " LastSeen " ) ] ) { device . last_seen = val . getUInt ( ) ; }
}
break ;
}
}
return true ;
}
//
// Command `ZbData`
//
void CmndZbData ( void ) {
if ( zigbee . init_phase ) { ResponseCmndChar_P ( PSTR ( D_ZIGBEE_NOT_STARTED ) ) ; return ; }
2020-10-10 16:00:37 +01:00
RemoveSpace ( XdrvMailbox . data ) ;
2020-10-07 19:04:33 +01:00
if ( XdrvMailbox . data [ 0 ] = = ' { ' ) {
// JSON input, enter saved data into memory -- essentially for debugging
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
if ( ! root ) { ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ; return ; }
// Skip `ZbData` if present
JsonParserToken zbdata = root . getObject ( ) . findStartsWith ( PSTR ( " ZbData " ) ) ;
if ( zbdata ) {
root = zbdata ;
}
for ( auto device_name : root ) {
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( device_name . getStr ( ) ) ;
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
Z_Device & device = zigbee_devices . getShortAddr ( shortaddr ) ;
JsonParserObject inner_data = device_name . getValue ( ) . getObject ( ) ;
if ( inner_data ) {
if ( ! parseDeviceInnerData ( device , inner_data ) ) {
return ;
}
}
}
ResponseCmndDone ( ) ;
} else {
// non-JSON, export current data
// ZbData 0x1234
// ZbData Device_Name
uint16_t shortaddr = zigbee_devices . parseDeviceParam ( XdrvMailbox . data ) ;
if ( BAD_SHORTADDR = = shortaddr ) { ResponseCmndChar_P ( PSTR ( " Unknown device " ) ) ; return ; }
const Z_Device & device = zigbee_devices . findShortAddr ( shortaddr ) ;
Z_attribute_list attr_data ;
{ // scope to force object deallocation
Z_attribute_list device_attr ;
device . toAttributes ( device_attr ) ;
attr_data . addAttribute ( F ( " _00 " ) ) . setStrRaw ( device_attr . toString ( true ) . c_str ( ) ) ;
}
// Iterate on data objects
for ( auto & data_elt : device . data ) {
Z_attribute_list inner_attr ;
char key [ 4 ] ;
snprintf_P ( key , sizeof ( key ) , " ?%02X " , data_elt . getEndpoint ( ) ) ;
2020-10-13 21:45:11 +01:00
// The key is in the form "L01", where 'L' is the type and '01' the endpoint in hex format
2020-10-07 19:04:33 +01:00
// 'P' = Power
2020-10-13 21:45:11 +01:00
// 'L' = Light
// 'O' = OnOff
// 'T' = Thermo & sensors
// 'A' = Alarm
// '?' = Device wide
2020-10-07 19:04:33 +01:00
//
2020-10-09 18:10:36 +01:00
Z_Data_Type data_type = data_elt . getType ( ) ;
switch ( data_type ) {
2020-10-07 19:04:33 +01:00
case Z_Data_Type : : Z_Plug :
{
key [ 0 ] = ' P ' ;
2020-10-09 18:10:36 +01:00
( ( Z_Data_Plug & ) data_elt ) . toAttributes ( inner_attr , data_type ) ;
2020-10-07 19:04:33 +01:00
}
break ;
case Z_Data_Type : : Z_Light :
{
key [ 0 ] = ' L ' ;
2020-10-09 18:10:36 +01:00
( ( Z_Data_Light & ) data_elt ) . toAttributes ( inner_attr , data_type ) ;
2020-10-07 19:04:33 +01:00
}
break ;
case Z_Data_Type : : Z_OnOff :
{
key [ 0 ] = ' O ' ;
2020-10-09 18:10:36 +01:00
( ( Z_Data_OnOff & ) data_elt ) . toAttributes ( inner_attr , data_type ) ;
2020-10-07 19:04:33 +01:00
}
break ;
case Z_Data_Type : : Z_Thermo :
{
key [ 0 ] = ' T ' ;
2020-10-09 18:10:36 +01:00
( ( Z_Data_Thermo & ) data_elt ) . toAttributes ( inner_attr , data_type ) ;
2020-10-07 19:04:33 +01:00
}
break ;
case Z_Data_Type : : Z_Alarm :
{
key [ 0 ] = ' A ' ;
2020-10-09 18:10:36 +01:00
( ( Z_Data_Alarm & ) data_elt ) . toAttributes ( inner_attr , data_type ) ;
2020-10-07 19:04:33 +01:00
}
break ;
}
if ( key [ 0 ] ! = ' ? ' ) {
attr_data . addAttribute ( key ) . setStrRaw ( inner_attr . toString ( true ) . c_str ( ) ) ;
}
}
char hex [ 8 ] ;
snprintf_P ( hex , sizeof ( hex ) , PSTR ( " 0x%04X " ) , shortaddr ) ;
Response_P ( PSTR ( " { \" %s \" :{ \" %s \" :%s}} " ) , XdrvMailbox . command , hex , attr_data . toString ( true ) . c_str ( ) ) ;
}
}
2020-04-11 17:50:46 +01:00
//
// Command `ZbConfig`
//
void CmndZbConfig ( void ) {
// ZbConfig
// ZbConfig {"Channel":11,"PanID":"0x1A63","ExtPanID":"0xCCCCCCCCCCCCCCCC","KeyL":"0x0F0D0B0907050301L","KeyH":"0x0D0C0A0806040200L"}
uint8_t zb_channel = Settings . zb_channel ;
uint16_t zb_pan_id = Settings . zb_pan_id ;
uint64_t zb_ext_panid = Settings . zb_ext_panid ;
uint64_t zb_precfgkey_l = Settings . zb_precfgkey_l ;
uint64_t zb_precfgkey_h = Settings . zb_precfgkey_h ;
2020-10-10 15:19:37 +01:00
int8_t zb_txradio_dbm = Settings . zb_txradio_dbm ;
2020-04-11 17:50:46 +01:00
// if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
2020-10-10 16:00:37 +01:00
RemoveSpace ( XdrvMailbox . data ) ;
2020-04-11 17:50:46 +01:00
if ( strlen ( XdrvMailbox . data ) > 0 ) {
2020-09-23 18:38:24 +01:00
JsonParser parser ( XdrvMailbox . data ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-21 20:49:32 +01:00
if ( ! root ) { ResponseCmndChar_P ( PSTR ( D_JSON_INVALID_JSON ) ) ; return ; }
2020-04-11 17:50:46 +01:00
// Channel
2020-09-21 20:49:32 +01:00
zb_channel = root . getUInt ( PSTR ( " Channel " ) , zb_channel ) ;
zb_pan_id = root . getUInt ( PSTR ( " PanID " ) , zb_pan_id ) ;
zb_ext_panid = root . getULong ( PSTR ( " ExtPanID " ) , zb_ext_panid ) ;
zb_precfgkey_l = root . getULong ( PSTR ( " KeyL " ) , zb_precfgkey_l ) ;
zb_precfgkey_h = root . getULong ( PSTR ( " KeyH " ) , zb_precfgkey_h ) ;
2020-10-10 15:19:37 +01:00
zb_txradio_dbm = root . getInt ( PSTR ( " TxRadio " ) , zb_txradio_dbm ) ;
2020-09-21 20:49:32 +01:00
2020-04-11 18:01:39 +01:00
if ( zb_channel < 11 ) { zb_channel = 11 ; }
if ( zb_channel > 26 ) { zb_channel = 26 ; }
2020-09-14 21:06:19 +01:00
// if network key is zero, we generate a truly random key with a hardware generator from ESP
if ( ( 0 = = zb_precfgkey_l ) & & ( 0 = = zb_precfgkey_h ) ) {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " generating random Zigbee network key " ) ) ;
zb_precfgkey_l = ( uint64_t ) HwRandom ( ) < < 32 | HwRandom ( ) ;
zb_precfgkey_h = ( uint64_t ) HwRandom ( ) < < 32 | HwRandom ( ) ;
}
2020-04-11 17:50:46 +01:00
// Check if a parameter was changed after all
if ( ( zb_channel ! = Settings . zb_channel ) | |
( zb_pan_id ! = Settings . zb_pan_id ) | |
( zb_ext_panid ! = Settings . zb_ext_panid ) | |
( zb_precfgkey_l ! = Settings . zb_precfgkey_l ) | |
2020-07-02 21:56:37 +01:00
( zb_precfgkey_h ! = Settings . zb_precfgkey_h ) | |
( zb_txradio_dbm ! = Settings . zb_txradio_dbm ) ) {
2020-04-11 17:50:46 +01:00
Settings . zb_channel = zb_channel ;
Settings . zb_pan_id = zb_pan_id ;
Settings . zb_ext_panid = zb_ext_panid ;
Settings . zb_precfgkey_l = zb_precfgkey_l ;
Settings . zb_precfgkey_h = zb_precfgkey_h ;
2020-07-02 21:56:37 +01:00
Settings . zb_txradio_dbm = zb_txradio_dbm ;
2020-04-11 17:50:46 +01:00
restart_flag = 2 ; // save and reboot
}
}
// display the current or new configuration
char hex_ext_panid [ 20 ] = " 0x " ;
Uint64toHex ( zb_ext_panid , & hex_ext_panid [ 2 ] , 64 ) ;
char hex_precfgkey_l [ 20 ] = " 0x " ;
Uint64toHex ( zb_precfgkey_l , & hex_precfgkey_l [ 2 ] , 64 ) ;
char hex_precfgkey_h [ 20 ] = " 0x " ;
Uint64toHex ( zb_precfgkey_h , & hex_precfgkey_h [ 2 ] , 64 ) ;
// {"ZbConfig":{"Channel":11,"PanID":"0x1A63","ExtPanID":"0xCCCCCCCCCCCCCCCC","KeyL":"0x0F0D0B0907050301L","KeyH":"0x0D0C0A0806040200L"}}
Response_P ( PSTR ( " { \" " D_PRFX_ZB D_JSON_ZIGBEE_CONFIG " \" :{ "
" \" Channel \" :%d "
" , \" PanID \" : \" 0x%04X \" "
" , \" ExtPanID \" : \" %s \" "
" , \" KeyL \" : \" %s \" "
" , \" KeyH \" : \" %s \" "
2020-07-02 21:56:37 +01:00
" , \" TxRadio \" :%d "
2020-04-11 17:50:46 +01:00
" }} " ) ,
zb_channel , zb_pan_id ,
hex_ext_panid ,
2020-07-02 21:56:37 +01:00
hex_precfgkey_l , hex_precfgkey_h ,
zb_txradio_dbm ) ;
2020-04-11 17:50:46 +01:00
}
2020-06-27 17:17:40 +01:00
/*********************************************************************************************\
* Presentation
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-08-23 15:16:32 +01:00
extern " C " {
int device_cmp ( const void * a , const void * b ) {
const Z_Device & dev_a = zigbee_devices . devicesAt ( * ( uint8_t * ) a ) ;
const Z_Device & dev_b = zigbee_devices . devicesAt ( * ( uint8_t * ) b ) ;
const char * fn_a = dev_a . friendlyName ;
const char * fn_b = dev_b . friendlyName ;
if ( fn_a & & fn_b ) {
return strcasecmp ( fn_a , fn_b ) ;
} else if ( ! fn_a & & ! fn_b ) {
return ( int32_t ) dev_a . shortaddr - ( int32_t ) dev_b . shortaddr ;
} else {
if ( fn_a ) return - 1 ;
return 1 ;
}
}
2020-09-03 03:16:13 +01:00
// Convert seconds to a string representing days, hours or minutes present in the n-value.
// The string will contain the most coarse time only, rounded down (61m == 01h, 01h37m == 01h).
// Inputs:
// - n: uint32_t representing some number of seconds
// - result: a buffer of suitable size (7 bytes would represent the entire solution space
// for UINT32_MAX including the trailing null-byte, or "49710d")
// - result_len: A numeric value representing the total length of the result buffer
// Returns:
// - The number of characters that would have been written were result sufficiently large
// - negatve number on encoding error from snprintf
//
int convert_seconds_to_dhm ( uint32_t n , char * result , size_t result_len ) {
char fmtstr [ ] = " %02dmhd " ; // Don't want this in progmem, because we mutate it.
uint32_t conversions [ 3 ] = { 24 * 3600 , 3600 , 60 } ;
uint32_t value ;
for ( int i = 0 ; i < 3 ; + + i ) {
value = n / conversions [ i ] ;
if ( value > 0 ) {
fmtstr [ 4 ] = fmtstr [ 6 - i ] ;
break ;
}
n = n % conversions [ i ] ;
}
// Null-terminate the string at the last "valid" index, removing any excess zero values.
fmtstr [ 5 ] = ' \0 ' ;
return snprintf ( result , result_len , fmtstr , value ) ;
}
}
2020-06-27 17:17:40 +01:00
void ZigbeeShow ( bool json )
{
if ( json ) {
return ;
# ifdef USE_WEBSERVER
} else {
uint32_t zigbee_num = zigbee_devices . devicesSize ( ) ;
2020-06-29 10:53:31 +01:00
if ( ! zigbee_num ) { return ; }
2020-08-23 15:16:32 +01:00
if ( zigbee_num > 255 ) { zigbee_num = 255 ; }
2020-06-29 10:53:31 +01:00
WSContentSend_P ( PSTR ( " </table>{t} " ) ) ; // Terminate current two column table and open new table
2020-09-03 03:16:13 +01:00
WSContentSend_P ( PSTR (
" <style> "
// Table CSS
" .ztd td:not(:first-child){width:20px;font-size:70%%} "
" .ztd td:last-child{width:45px} "
" .ztd .bt{margin-right:10px;} " // Margin right should be half of the not-first width
2020-09-25 17:42:00 +01:00
" .htr{line-height:20px} "
2020-09-03 03:16:13 +01:00
// Lighting
" .bx{height:14px;width:14px;display:inline-block;border:1px solid currentColor;background-color:var(--cl,#fff)} "
// Signal Strength Indicator
" .ssi{display:inline-flex;align-items:flex-end;height:15px;padding:0} "
" .ssi i{width:3px;margin-right:1px;border-radius:3px;background-color:#eee} "
" .ssi .b0{height:25%%}.ssi .b1{height:50%%}.ssi .b2{height:75%%}.ssi .b3{height:100%%}.o30{opacity:.3} "
" </style> "
) ) ;
2020-06-29 10:53:31 +01:00
2020-08-23 15:16:32 +01:00
// sort elements by name, then by id
uint8_t sorted_idx [ zigbee_num ] ;
for ( uint32_t i = 0 ; i < zigbee_num ; i + + ) {
sorted_idx [ i ] = i ;
}
qsort ( sorted_idx , zigbee_num , sizeof ( sorted_idx [ 0 ] ) , device_cmp ) ;
2020-09-03 03:16:13 +01:00
uint32_t now = Rtc . utc_time ;
2020-06-27 17:17:40 +01:00
for ( uint32_t i = 0 ; i < zigbee_num ; i + + ) {
2020-08-23 15:16:32 +01:00
const Z_Device & device = zigbee_devices . devicesAt ( sorted_idx [ i ] ) ;
2020-08-22 17:40:44 +01:00
uint16_t shortaddr = device . shortaddr ;
2020-09-03 03:16:13 +01:00
char * name = ( char * ) device . friendlyName ;
2020-06-29 10:53:31 +01:00
2020-09-03 03:16:13 +01:00
char sdevice [ 33 ] ;
if ( nullptr = = name ) {
snprintf_P ( sdevice , sizeof ( sdevice ) , PSTR ( D_DEVICE " 0x%04X " ) , shortaddr ) ;
name = sdevice ;
}
2020-06-29 10:53:31 +01:00
2020-09-03 03:16:13 +01:00
char sbatt [ 64 ] ;
snprintf_P ( sbatt , sizeof ( sbatt ) , PSTR ( " " ) ) ;
if ( device . validBatteryPercent ( ) ) {
snprintf_P ( sbatt , sizeof ( sbatt ) ,
PSTR ( " <i class= \" bt \" title= \" %d%% \" style= \" --bl:%dpx \" ></i> " ) ,
device . batterypercent , changeUIntScale ( device . batterypercent , 0 , 100 , 0 , 14 )
) ;
}
2020-08-22 17:40:44 +01:00
2020-09-03 03:16:13 +01:00
uint32_t num_bars = 0 ;
char slqi [ 4 ] ;
slqi [ 0 ] = ' - ' ;
slqi [ 1 ] = ' \0 ' ;
if ( device . validLqi ( ) ) {
num_bars = changeUIntScale ( device . lqi , 0 , 254 , 0 , 4 ) ;
snprintf_P ( slqi , sizeof ( slqi ) , PSTR ( " %d " ) , device . lqi ) ;
2020-06-27 17:17:40 +01:00
}
2020-09-03 03:16:13 +01:00
WSContentSend_PD ( PSTR (
2020-09-25 17:42:00 +01:00
" <tr class='ztd htr'> "
2020-09-03 03:16:13 +01:00
" <td><b>%s</b></td> " // name
" <td>%s</td> " // sbatt (Battery Indicator)
" <td><div title=' " D_LQI " %s' class='ssi'> " // slqi
) , name , sbatt , slqi ) ;
if ( device . validLqi ( ) ) {
for ( uint32_t j = 0 ; j < 4 ; + + j ) {
WSContentSend_PD ( PSTR ( " <i class='b%d%s'></i> " ) , j , ( num_bars < j ) ? PSTR ( " o30 " ) : PSTR ( " " ) ) ;
}
}
char dhm [ 16 ] ; // len("🕗" + "49710d" + '\0') == 16
snprintf_P ( dhm , sizeof ( dhm ) , PSTR ( " " ) ) ;
if ( device . validLastSeen ( ) ) {
snprintf_P ( dhm , sizeof ( dhm ) , PSTR ( " 🕗 " ) ) ;
convert_seconds_to_dhm ( now - device . last_seen , & dhm [ 9 ] , 7 ) ;
}
WSContentSend_PD ( PSTR (
" </div></td> " // Close LQI
" <td>%s{e} " // dhm (Last Seen)
) , dhm ) ;
// Sensors
2020-10-07 19:04:33 +01:00
const Z_Data_Thermo & thermo = device . data . find < Z_Data_Thermo > ( ) ;
2020-08-22 17:40:44 +01:00
2020-10-07 19:04:33 +01:00
if ( & thermo ! = nullptr ) {
2020-09-25 17:42:00 +01:00
WSContentSend_P ( PSTR ( " <tr class='htr'><td colspan= \" 4 \" >┆ " ) ) ;
2020-10-07 19:04:33 +01:00
if ( thermo . validTemperature ( ) ) {
2020-08-22 17:40:44 +01:00
char buf [ 12 ] ;
2020-10-07 19:04:33 +01:00
dtostrf ( thermo . getTemperature ( ) / 100.0f , 3 , 1 , buf ) ;
2020-08-28 21:53:34 +01:00
WSContentSend_PD ( PSTR ( " ☀️ %s°C " ) , buf ) ;
2020-08-22 17:40:44 +01:00
}
2020-10-07 19:04:33 +01:00
if ( thermo . validTempTarget ( ) ) {
2020-10-02 21:30:11 +01:00
char buf [ 12 ] ;
2020-10-07 19:04:33 +01:00
dtostrf ( thermo . getTempTarget ( ) / 100.0f , 3 , 1 , buf ) ;
2020-10-02 21:30:11 +01:00
WSContentSend_PD ( PSTR ( " 🎯 %s°C " ) , buf ) ;
}
2020-10-07 19:04:33 +01:00
if ( thermo . validThSetpoint ( ) ) {
WSContentSend_PD ( PSTR ( " ⚙️ %d%% " ) , thermo . getThSetpoint ( ) ) ;
2020-10-02 21:30:11 +01:00
}
2020-10-07 19:04:33 +01:00
if ( thermo . validHumidity ( ) ) {
WSContentSend_P ( PSTR ( " 💧 %d%% " ) , ( uint16_t ) ( thermo . getHumidity ( ) / 100.0f + 0.5f ) ) ;
2020-08-22 17:40:44 +01:00
}
2020-10-07 19:04:33 +01:00
if ( thermo . validPressure ( ) ) {
WSContentSend_P ( PSTR ( " ⛅ %d hPa " ) , thermo . getPressure ( ) ) ;
2020-08-22 17:40:44 +01:00
}
2020-09-03 03:16:13 +01:00
2020-08-22 17:40:44 +01:00
WSContentSend_P ( PSTR ( " {e} " ) ) ;
2020-06-29 12:52:24 +01:00
}
2020-08-26 07:56:13 +01:00
2020-08-28 21:53:34 +01:00
// Light, switches and plugs
2020-10-07 19:04:33 +01:00
const Z_Data_OnOff & onoff = device . data . find < Z_Data_OnOff > ( ) ;
const Z_Data_Light & light = device . data . find < Z_Data_Light > ( ) ;
bool light_display = ( & light ! = nullptr ) ? light . validDimmer ( ) : false ;
const Z_Data_Plug & plug = device . data . find < Z_Data_Plug > ( ) ;
if ( ( & onoff ! = nullptr ) | | light_display | | ( & plug ! = nullptr ) ) {
int8_t channels = device . getLightChannels ( ) ;
if ( channels < 0 ) { channels = 5 ; } // if number of channel is unknown, display all known attributes
WSContentSend_P ( PSTR ( " <tr class='htr'><td colspan= \" 4 \" >┆ " ) ) ;
if ( & onoff ! = nullptr ) {
WSContentSend_P ( PSTR ( " %s " ) , device . getPower ( ) ? PSTR ( D_ON ) : PSTR ( D_OFF ) ) ;
2020-08-26 07:56:13 +01:00
}
2020-10-07 19:04:33 +01:00
if ( & light ! = nullptr ) {
if ( light . validDimmer ( ) & & ( channels > = 1 ) ) {
WSContentSend_P ( PSTR ( " 🔅 %d%% " ) , changeUIntScale ( light . getDimmer ( ) , 0 , 254 , 0 , 100 ) ) ;
}
if ( light . validCT ( ) & & ( ( channels = = 2 ) | | ( channels = = 5 ) ) ) {
uint32_t ct_k = ( ( ( 1000000 / light . getCT ( ) ) + 25 ) / 50 ) * 50 ;
WSContentSend_P ( PSTR ( " <span title= \" CT %d \" ><small>⚪ </small>%dK</span> " ) , light . getCT ( ) , ct_k ) ;
}
if ( light . validHue ( ) & & light . validSat ( ) & & ( channels > = 3 ) ) {
uint8_t r , g , b ;
uint8_t sat = changeUIntScale ( light . getSat ( ) , 0 , 254 , 0 , 255 ) ; // scale to 0..255
LightStateClass : : HsToRgb ( light . getHue ( ) , sat , & r , & g , & b ) ;
WSContentSend_P ( PSTR ( " <i class= \" bx \" style= \" --cl:#%02X%02X%02X \" ></i>#%02X%02X%02X " ) , r , g , b , r , g , b ) ;
} else if ( light . validX ( ) & & light . validY ( ) & & ( channels > = 3 ) ) {
uint8_t r , g , b ;
LightStateClass : : XyToRgb ( light . getX ( ) / 65535.0f , light . getY ( ) / 65535.0f , & r , & g , & b ) ;
WSContentSend_P ( PSTR ( " <i class= \" bx \" style= \" --cl:#%02X%02X%02X \" ></i> #%02X%02X%02X " ) , r , g , b , r , g , b ) ;
}
2020-08-26 07:56:13 +01:00
}
2020-10-07 19:04:33 +01:00
if ( & plug ! = nullptr ) {
2020-08-28 21:53:34 +01:00
WSContentSend_P ( PSTR ( " ⚡ " ) ) ;
2020-10-07 19:04:33 +01:00
if ( plug . validMainsVoltage ( ) ) {
WSContentSend_P ( PSTR ( " %dV " ) , plug . getMainsVoltage ( ) ) ;
2020-08-28 21:53:34 +01:00
}
2020-10-07 19:04:33 +01:00
if ( plug . validMainsPower ( ) ) {
WSContentSend_P ( PSTR ( " %dW " ) , plug . getMainsPower ( ) ) ;
2020-08-28 21:53:34 +01:00
}
}
2020-09-03 03:16:13 +01:00
WSContentSend_P ( PSTR ( " {e} " ) ) ;
2020-08-26 07:56:13 +01:00
}
2020-06-27 17:17:40 +01:00
}
2020-06-29 10:53:31 +01:00
WSContentSend_P ( PSTR ( " </table>{t} " ) ) ; // Terminate current multi column table and open new table
2020-06-27 17:17:40 +01:00
# endif
}
}
2019-08-31 20:23:32 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv23 ( uint8_t function )
{
bool result = false ;
if ( zigbee . active ) {
switch ( function ) {
2019-12-15 15:02:41 +00:00
case FUNC_EVERY_50_MSECOND :
if ( ! zigbee . init_phase ) {
zigbee_devices . runTimer ( ) ;
}
break ;
2019-08-31 20:23:32 +01:00
case FUNC_LOOP :
2020-07-26 17:55:31 +01:00
# ifdef USE_ZIGBEE_EZSP
if ( ZigbeeUploadXmodem ( ) ) {
return false ;
}
# endif
2020-07-22 18:29:16 +01:00
if ( ZigbeeSerial ) {
ZigbeeInputLoop ( ) ;
ZigbeeOutputLoop ( ) ; // send any outstanding data
2020-10-19 19:34:40 +01:00
# ifdef USE_ZIGBEE_EZSP
ZigbeeGlowPermitJoinLight ( ) ;
# endif // USE_ZIGBEE_EZSP
2020-07-22 18:29:16 +01:00
}
2020-07-26 17:55:31 +01:00
if ( zigbee . state_machine ) {
2019-08-31 20:23:32 +01:00
ZigbeeStateMachine_Run ( ) ;
2020-07-26 17:55:31 +01:00
}
2019-08-31 20:23:32 +01:00
break ;
2020-06-27 17:17:40 +01:00
# ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR :
ZigbeeShow ( false ) ;
break ;
2020-09-01 12:05:46 +01:00
# ifdef USE_ZIGBEE_EZSP
// GUI xmodem
case FUNC_WEB_ADD_HANDLER :
Webserver - > on ( " / " WEB_HANDLE_ZIGBEE_XFER , HandleZigbeeXfer ) ;
break ;
# endif // USE_ZIGBEE_EZSP
2020-06-27 17:17:40 +01:00
# endif // USE_WEBSERVER
2019-08-31 20:23:32 +01:00
case FUNC_PRE_INIT :
ZigbeeInit ( ) ;
break ;
case FUNC_COMMAND :
2020-01-25 16:42:53 +00:00
result = DecodeCommand ( kZbCommands , ZigbeeCommand ) ;
2019-08-31 20:23:32 +01:00
break ;
}
}
return result ;
}
# endif // USE_ZIGBEE