2020-10-07 19:04:33 +01:00
/*
xdrv_23_zigbee_2a_devices_impl . 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
/*********************************************************************************************\
* Implementation
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-11-01 18:00:07 +00:00
Z_Device & Z_Devices : : devicesAt ( size_t i ) const {
Z_Device * devp = ( Z_Device * ) _devices . at ( i ) ;
if ( devp ) {
return * devp ;
} else {
return device_unk ;
}
}
2020-10-07 19:04:33 +01:00
//
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
// entry with same shortaddr or longaddr exists.
//
Z_Device & Z_Devices : : createDeviceEntry ( uint16_t shortaddr , uint64_t longaddr ) {
2020-11-01 18:00:07 +00:00
if ( ( BAD_SHORTADDR = = shortaddr ) & & ! longaddr ) { return device_unk ; } // it is not legal to create this entry
2020-10-28 09:08:15 +00:00
Z_Device & device = _devices . addToLast ( ) ;
device . shortaddr = shortaddr ;
device . longaddr = longaddr ;
2020-10-07 19:04:33 +01:00
dirty ( ) ;
2020-10-28 09:08:15 +00:00
return device ;
2020-10-07 19:04:33 +01:00
}
void Z_Devices : : freeDeviceEntry ( Z_Device * device ) {
if ( device - > manufacturerId ) { free ( device - > manufacturerId ) ; }
if ( device - > modelId ) { free ( device - > modelId ) ; }
if ( device - > friendlyName ) { free ( device - > friendlyName ) ; }
free ( device ) ;
}
//
// Scan all devices to find a corresponding shortaddr
// Looks info device.shortaddr entry
// In:
// shortaddr (not BAD_SHORTADDR)
// Out:
// reference to device, or to device_unk if not found
// (use foundDevice() to check if found)
Z_Device & Z_Devices : : findShortAddr ( uint16_t shortaddr ) {
for ( auto & elem : _devices ) {
if ( elem . shortaddr = = shortaddr ) { return elem ; }
}
2020-11-01 18:00:07 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
const Z_Device & Z_Devices : : findShortAddr ( uint16_t shortaddr ) const {
for ( const auto & elem : _devices ) {
if ( elem . shortaddr = = shortaddr ) { return elem ; }
}
return device_unk ;
}
//
// Scan all devices to find a corresponding longaddr
// Looks info device.longaddr entry
// In:
// longaddr (non null)
// Out:
// index in _devices of entry, -1 if not found
//
Z_Device & Z_Devices : : findLongAddr ( uint64_t longaddr ) {
2020-11-01 18:00:07 +00:00
if ( ! longaddr ) { return device_unk ; }
2020-10-07 19:04:33 +01:00
for ( auto & elem : _devices ) {
if ( elem . longaddr = = longaddr ) { return elem ; }
}
2020-11-01 18:00:07 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
const Z_Device & Z_Devices : : findLongAddr ( uint64_t longaddr ) const {
if ( ! longaddr ) { return device_unk ; }
for ( const auto & elem : _devices ) {
if ( elem . longaddr = = longaddr ) { return elem ; }
}
return device_unk ;
}
//
// Scan all devices to find a corresponding friendlyNme
// Looks info device.friendlyName entry
// In:
// friendlyName (null terminated, should not be empty)
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices : : findFriendlyName ( const char * name ) const {
if ( ! name ) { return - 1 ; } // if pointer is null
size_t name_len = strlen ( name ) ;
int32_t found = 0 ;
if ( name_len ) {
for ( auto & elem : _devices ) {
if ( elem . friendlyName ) {
if ( strcasecmp ( elem . friendlyName , name ) = = 0 ) { return found ; }
}
found + + ;
}
}
return - 1 ;
}
2020-11-01 18:00:07 +00:00
Z_Device & Z_Devices : : isKnownLongAddrDevice ( uint64_t longaddr ) const {
return ( Z_Device & ) findLongAddr ( longaddr ) ;
2020-10-07 19:04:33 +01:00
}
2020-11-01 18:00:07 +00:00
Z_Device & Z_Devices : : isKnownIndexDevice ( uint32_t index ) const {
2020-10-07 19:04:33 +01:00
if ( index < devicesSize ( ) ) {
2020-11-01 18:00:07 +00:00
return devicesAt ( index ) ;
2020-10-07 19:04:33 +01:00
} else {
2020-11-01 18:00:07 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
}
2020-11-01 18:00:07 +00:00
Z_Device & Z_Devices : : isKnownFriendlyNameDevice ( const char * name ) const {
if ( ( ! name ) | | ( 0 = = strlen ( name ) ) ) { return device_unk ; } // Error
2020-10-07 19:04:33 +01:00
int32_t found = findFriendlyName ( name ) ;
if ( found > = 0 ) {
2020-11-01 18:00:07 +00:00
return devicesAt ( found ) ;
2020-10-07 19:04:33 +01:00
} else {
2020-11-01 18:00:07 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
}
uint64_t Z_Devices : : getDeviceLongAddr ( uint16_t shortaddr ) const {
return findShortAddr ( shortaddr ) . longaddr ; // if unknown, it reverts to the Unknown device and longaddr is 0x00
}
//
// We have a seen a shortaddr on the network, get the corresponding device object
//
Z_Device & Z_Devices : : getShortAddr ( uint16_t shortaddr ) {
2020-11-01 18:00:07 +00:00
if ( BAD_SHORTADDR = = shortaddr ) { return device_unk ; } // this is not legal
2020-10-07 19:04:33 +01:00
Z_Device & device = findShortAddr ( shortaddr ) ;
if ( foundDevice ( device ) ) {
return device ;
}
return createDeviceEntry ( shortaddr , 0 ) ;
}
// find the Device object by its longaddr (unique key if not null)
Z_Device & Z_Devices : : getLongAddr ( uint64_t longaddr ) {
2020-11-01 18:00:07 +00:00
if ( ! longaddr ) { return device_unk ; }
2020-10-07 19:04:33 +01:00
Z_Device & device = findLongAddr ( longaddr ) ;
if ( foundDevice ( device ) ) {
return device ;
}
return createDeviceEntry ( 0 , longaddr ) ;
}
// Remove device from list, return true if it was known, false if it was not recorded
bool Z_Devices : : removeDevice ( uint16_t shortaddr ) {
Z_Device & device = findShortAddr ( shortaddr ) ;
if ( foundDevice ( device ) ) {
_devices . remove ( & device ) ;
dirty ( ) ;
return true ;
}
return false ;
}
//
// We have just seen a device on the network, update the info based on short/long addr
// In:
// shortaddr
// longaddr (both can't be null at the same time)
2020-10-28 09:08:15 +00:00
Z_Device & Z_Devices : : updateDevice ( uint16_t shortaddr , uint64_t longaddr ) {
2020-10-07 19:04:33 +01:00
Z_Device * s_found = & findShortAddr ( shortaddr ) ; // is there already a shortaddr entry
Z_Device * l_found = & findLongAddr ( longaddr ) ; // is there already a longaddr entry
if ( foundDevice ( * s_found ) & & foundDevice ( * l_found ) ) { // both shortaddr and longaddr are already registered
if ( s_found = = l_found ) {
} else { // they don't match
// the device with longaddr got a new shortaddr
l_found - > shortaddr = shortaddr ; // update the shortaddr corresponding to the longaddr
// erase the previous shortaddr
freeDeviceEntry ( s_found ) ;
_devices . remove ( s_found ) ;
dirty ( ) ;
2020-10-28 09:08:15 +00:00
return * l_found ;
2020-10-07 19:04:33 +01:00
}
} else if ( foundDevice ( * s_found ) ) {
// shortaddr already exists but longaddr not
// add the longaddr to the entry
s_found - > longaddr = longaddr ;
dirty ( ) ;
2020-10-28 09:08:15 +00:00
return * s_found ;
2020-10-07 19:04:33 +01:00
} else if ( foundDevice ( * l_found ) ) {
// longaddr entry exists, update shortaddr
l_found - > shortaddr = shortaddr ;
dirty ( ) ;
2020-10-28 09:08:15 +00:00
return * l_found ;
2020-10-07 19:04:33 +01:00
} else {
// neither short/lonf addr are found.
if ( ( BAD_SHORTADDR ! = shortaddr ) | | longaddr ) {
2020-10-28 09:08:15 +00:00
return createDeviceEntry ( shortaddr , longaddr ) ;
2020-10-07 19:04:33 +01:00
}
2020-11-01 18:00:07 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
2020-11-12 18:38:21 +00:00
return device_unk ;
2020-10-07 19:04:33 +01:00
}
//
// Clear all endpoints
//
2020-10-31 18:51:17 +00:00
void Z_Device : : clearEndpoints ( void ) {
2020-10-07 19:04:33 +01:00
for ( uint32_t i = 0 ; i < endpoints_max ; i + + ) {
2020-10-31 18:51:17 +00:00
endpoints [ i ] = 0 ;
2020-10-07 19:04:33 +01:00
// no dirty here because it doesn't make sense to store it, does it?
}
}
//
// Add an endpoint to a shortaddr
2020-10-31 16:48:40 +00:00
// return true if a change was made
2020-10-07 19:04:33 +01:00
//
2020-10-31 16:48:40 +00:00
bool Z_Device : : addEndpoint ( uint8_t endpoint ) {
if ( ( 0x00 = = endpoint ) | | ( endpoint > 240 ) ) { return false ; }
2020-10-07 19:04:33 +01:00
for ( uint32_t i = 0 ; i < endpoints_max ; i + + ) {
2020-10-31 16:48:40 +00:00
if ( endpoint = = endpoints [ i ] ) {
return false ; // endpoint already there
2020-10-07 19:04:33 +01:00
}
2020-10-31 16:48:40 +00:00
if ( 0 = = endpoints [ i ] ) {
endpoints [ i ] = endpoint ;
return true ;
2020-10-07 19:04:33 +01:00
}
}
2020-10-31 16:48:40 +00:00
return false ;
}
2020-10-07 19:04:33 +01:00
//
// Count the number of known endpoints
//
2020-10-31 18:51:17 +00:00
uint32_t Z_Device : : countEndpoints ( void ) const {
2020-10-07 19:04:33 +01:00
uint32_t count_ep = 0 ;
for ( uint32_t i = 0 ; i < endpoints_max ; i + + ) {
2020-10-31 18:51:17 +00:00
if ( 0 ! = endpoints [ i ] ) {
2020-10-07 19:04:33 +01:00
count_ep + + ;
}
}
return count_ep ;
}
// Find the first endpoint of the device
uint8_t Z_Devices : : findFirstEndpoint ( uint16_t shortaddr ) const {
// When in router of end-device mode, the coordinator was not probed, in this case always talk to endpoint 1
if ( 0x0000 = = shortaddr ) { return 1 ; }
return findShortAddr ( shortaddr ) . endpoints [ 0 ] ; // returns 0x00 if no endpoint
}
2020-10-31 18:51:17 +00:00
void Z_Device : : setStringAttribute ( char * & attr , const char * str ) {
2020-10-07 19:04:33 +01:00
if ( nullptr = = str ) { return ; } // ignore a null parameter
size_t str_len = strlen ( str ) ;
if ( ( nullptr = = attr ) & & ( 0 = = str_len ) ) { return ; } // if both empty, don't do anything
if ( attr ) {
// we already have a value
if ( strcmp ( attr , str ) ! = 0 ) {
// new value
free ( attr ) ; // free previous value
attr = nullptr ;
} else {
return ; // same value, don't change anything
}
}
if ( str_len ) {
2020-10-28 09:08:15 +00:00
if ( str_len > 31 ) { str_len = 31 ; }
2020-10-07 19:04:33 +01:00
attr = ( char * ) malloc ( str_len + 1 ) ;
strlcpy ( attr , str , str_len + 1 ) ;
}
2020-10-31 18:51:17 +00:00
zigbee_devices . dirty ( ) ;
2020-10-07 19:04:33 +01:00
}
//
// Sets the ManufId for a device.
// No action taken if the device does not exist.
// Inputs:
// - shortaddr: 16-bits short address of the device. No action taken if the device is unknown
// - str: string pointer, if nullptr it is considered as empty string
// Impact:
// - Any actual change in ManufId (i.e. setting a different value) triggers a `dirty()` and saving to Flash
//
2020-10-31 18:51:17 +00:00
void Z_Device : : setManufId ( const char * str ) {
setStringAttribute ( manufacturerId , str ) ;
2020-10-07 19:04:33 +01:00
}
2020-10-31 18:51:17 +00:00
void Z_Device : : setModelId ( const char * str ) {
setStringAttribute ( modelId , str ) ;
2020-10-07 19:04:33 +01:00
}
2020-10-31 18:51:17 +00:00
void Z_Device : : setFriendlyName ( const char * str ) {
setStringAttribute ( friendlyName , str ) ;
2020-10-07 19:04:33 +01:00
}
2020-11-01 18:00:07 +00:00
void Z_Device : : setLastSeenNow ( void ) {
2020-10-07 19:04:33 +01:00
// Only update time if after 2020-01-01 0000.
// Fixes issue where zigbee device pings before WiFi/NTP has set utc_time
// to the correct time, and "last seen" calculations are based on the
// pre-corrected last_seen time and the since-corrected utc_time.
2020-11-08 08:38:34 +00:00
if ( Rtc . utc_time < START_VALID_TIME ) { return ; }
2020-11-01 18:00:07 +00:00
last_seen = Rtc . utc_time ;
2020-10-07 19:04:33 +01:00
}
2020-11-22 15:07:09 +00:00
void Z_Devices : : deviceWasReached ( uint16_t shortaddr ) {
// since we just receveived data from the device, it is reachable
zigbee_devices . resetTimersForDevice ( shortaddr , 0 /* groupaddr */ , Z_CAT_REACHABILITY ) ; // remove any reachability timer already there
Z_Device & device = findShortAddr ( shortaddr ) ;
if ( device . valid ( ) ) {
device . setReachable ( true ) ; // mark device as reachable
}
}
2020-10-07 19:04:33 +01:00
// get the next sequance number for the device, or use the global seq number if device is unknown
uint8_t Z_Devices : : getNextSeqNumber ( uint16_t shortaddr ) {
Z_Device & device = findShortAddr ( shortaddr ) ;
if ( foundDevice ( device ) ) {
device . seqNumber + = 1 ;
return device . seqNumber ;
} else {
_seqNumber + = 1 ;
return _seqNumber ;
}
}
2020-11-01 18:00:07 +00:00
// returns: dirty flag, did we change the value of the object
void Z_Device : : setLightChannels ( int8_t channels ) {
if ( channels > = 0 ) {
// retrieve of create light object
Z_Data_Light & light = data . get < Z_Data_Light > ( 0 ) ;
if ( channels ! = light . getConfig ( ) ) {
light . setConfig ( channels ) ;
zigbee_devices . dirty ( ) ;
}
Z_Data_OnOff & onoff = data . get < Z_Data_OnOff > ( 0 ) ;
2020-11-12 18:38:21 +00:00
( void ) onoff ;
2020-11-01 18:00:07 +00:00
} else {
// remove light / onoff object if any
for ( auto & data_elt : data ) {
if ( ( data_elt . getType ( ) = = Z_Data_Type : : Z_Light ) | |
( data_elt . getType ( ) = = Z_Data_Type : : Z_OnOff ) ) {
// remove light object
data . remove ( & data_elt ) ;
zigbee_devices . dirty ( ) ;
}
}
2020-10-07 19:04:33 +01:00
}
}
int8_t Z_Devices : : getHueBulbtype ( uint16_t shortaddr ) const {
2020-11-01 18:00:07 +00:00
const Z_Device & device = findShortAddr ( shortaddr ) ;
int8_t light_profile = device . getLightChannels ( ) ;
2020-10-07 19:04:33 +01:00
if ( 0x00 = = ( light_profile & 0xF0 ) ) {
return ( light_profile & 0x07 ) ;
} else {
// not a bulb
return - 1 ;
}
}
void Z_Devices : : hideHueBulb ( uint16_t shortaddr , bool hidden ) {
Z_Device & device = getShortAddr ( shortaddr ) ;
if ( device . hidden ! = hidden ) {
device . hidden = hidden ;
dirty ( ) ;
}
}
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
bool Z_Devices : : isHueBulbHidden ( uint16_t shortaddr ) const {
const Z_Device & device = findShortAddr ( shortaddr ) ;
if ( foundDevice ( device ) ) {
return device . hidden ;
}
return true ; // Fallback - Device is considered as hidden
}
// Deferred actions
// Parse for a specific category, of all deferred for a device if category == 0xFF
// Only with specific cluster number or for all clusters if cluster == 0xFFFF
void Z_Devices : : resetTimersForDevice ( uint16_t shortaddr , uint16_t groupaddr , uint8_t category , uint16_t cluster , uint8_t endpoint ) {
// iterate the list of deferred, and remove any linked to the shortaddr
for ( auto & defer : _deferred ) {
if ( ( defer . shortaddr = = shortaddr ) & & ( defer . groupaddr = = groupaddr ) ) {
if ( ( 0xFF = = category ) | | ( defer . category = = category ) ) {
if ( ( 0xFFFF = = cluster ) | | ( defer . cluster = = cluster ) ) {
if ( ( 0xFF = = endpoint ) | | ( defer . endpoint = = endpoint ) ) {
_deferred . remove ( & defer ) ;
}
}
}
}
}
}
// Set timer for a specific device
void Z_Devices : : setTimer ( uint16_t shortaddr , uint16_t groupaddr , uint32_t wait_ms , uint16_t cluster , uint8_t endpoint , uint8_t category , uint32_t value , Z_DeviceTimer func ) {
// First we remove any existing timer for same device in same category, except for category=0x00 (they need to happen anyway)
if ( category > = Z_CLEAR_DEVICE ) { // if category == 0, we leave all previous timers
resetTimersForDevice ( shortaddr , groupaddr , category , category > = Z_CLEAR_DEVICE_CLUSTER ? cluster : 0xFFFF , category > = Z_CLEAR_DEVICE_CLUSTER_ENDPOINT ? endpoint : 0xFF ) ; // remove any cluster
}
// Now create the new timer
Z_Deferred & deferred = _deferred . addHead ( ) ;
deferred = { wait_ms + millis ( ) , // timer
shortaddr ,
groupaddr ,
cluster ,
endpoint ,
category ,
value ,
func } ;
}
// Set timer after the already queued events
// I.e. the wait_ms is not counted from now, but from the last event queued, which is 'now' or in the future
void Z_Devices : : queueTimer ( uint16_t shortaddr , uint16_t groupaddr , uint32_t wait_ms , uint16_t cluster , uint8_t endpoint , uint8_t category , uint32_t value , Z_DeviceTimer func ) {
Z_Device & device = getShortAddr ( shortaddr ) ;
uint32_t now_millis = millis ( ) ;
if ( TimeReached ( device . defer_last_message_sent ) ) {
device . defer_last_message_sent = now_millis ;
}
// defer_last_message_sent equals now or a value in the future
device . defer_last_message_sent + = wait_ms ;
// for queueing we don't clear the backlog, so we force category to Z_CAT_ALWAYS
setTimer ( shortaddr , groupaddr , ( device . defer_last_message_sent - now_millis ) , cluster , endpoint , Z_CAT_ALWAYS , value , func ) ;
}
// Run timer at each tick
// WARNING: don't set a new timer within a running timer, this causes memory corruption
void Z_Devices : : runTimer ( void ) {
// visit all timers
for ( auto & defer : _deferred ) {
uint32_t timer = defer . timer ;
if ( TimeReached ( timer ) ) {
( * defer . func ) ( defer . shortaddr , defer . groupaddr , defer . cluster , defer . endpoint , defer . value ) ;
_deferred . remove ( & defer ) ;
}
}
// check if we need to save to Flash
if ( ( _saveTimer ) & & TimeReached ( _saveTimer ) ) {
saveZigbeeDevices ( ) ;
_saveTimer = 0 ;
}
}
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
// false - new attributes can be safely added
bool Z_Devices : : jsonIsConflict ( uint16_t shortaddr , const Z_attribute_list & attr_list ) const {
const Z_Device & device = findShortAddr ( shortaddr ) ;
if ( ! foundDevice ( device ) ) { return false ; }
if ( attr_list . isEmpty ( ) ) {
return false ; // if no previous value, no conflict
}
// compare groups
if ( device . attr_list . isValidGroupId ( ) & & attr_list . isValidGroupId ( ) ) {
if ( device . attr_list . group_id ! = attr_list . group_id ) { return true ; } // groups are in conflict
}
// compare src_ep
if ( device . attr_list . isValidSrcEp ( ) & & attr_list . isValidSrcEp ( ) ) {
if ( device . attr_list . src_ep ! = attr_list . src_ep ) { return true ; }
}
2020-10-30 11:29:48 +00:00
2020-10-07 19:04:33 +01:00
// LQI does not count as conflicting
// parse all other parameters
for ( const auto & attr : attr_list ) {
const Z_attribute * curr_attr = device . attr_list . findAttribute ( attr ) ;
if ( nullptr ! = curr_attr ) {
if ( ! curr_attr - > equalsVal ( attr ) ) {
return true ; // the value already exists and is different - conflict!
}
}
}
return false ;
}
void Z_Devices : : jsonAppend ( uint16_t shortaddr , const Z_attribute_list & attr_list ) {
Z_Device & device = getShortAddr ( shortaddr ) ;
device . attr_list . mergeList ( attr_list ) ;
}
2020-11-11 11:09:18 +00:00
//
// internal function to publish device information with respect to all `SetOption`s
//
2020-11-13 12:32:45 +00:00
void Z_Device : : jsonPublishAttrList ( const char * json_prefix , const Z_attribute_list & attr_list ) const {
bool use_fname = ( Settings . flag4 . zigbee_use_names ) & & ( friendlyName ) ; // should we replace shortaddr with friendlyname?
2020-11-11 11:09:18 +00:00
TasmotaGlobal . mqtt_data [ 0 ] = 0 ; // clear string
// Do we prefix with `ZbReceived`?
if ( ! Settings . flag4 . remove_zbreceived ) {
2020-11-13 12:32:45 +00:00
Response_P ( PSTR ( " { \" %s \" : " ) , json_prefix ) ;
2020-11-11 11:09:18 +00:00
}
// What key do we use, shortaddr or name?
if ( use_fname ) {
2020-11-13 12:32:45 +00:00
Response_P ( PSTR ( " %s{ \" %s \" :{ " ) , TasmotaGlobal . mqtt_data , friendlyName ) ;
2020-11-11 11:09:18 +00:00
} else {
2020-11-13 12:32:45 +00:00
Response_P ( PSTR ( " %s{ \" 0x%04X \" :{ " ) , TasmotaGlobal . mqtt_data , shortaddr ) ;
2020-11-11 11:09:18 +00:00
}
// Add "Device":"0x...."
2020-11-13 12:32:45 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_ZIGBEE_DEVICE " \" : \" 0x%04X \" , " ) , shortaddr ) ;
2020-11-11 11:09:18 +00:00
// Add "Name":"xxx" if name is present
2020-11-13 12:32:45 +00:00
if ( friendlyName ) {
ResponseAppend_P ( PSTR ( " \" " D_JSON_ZIGBEE_NAME " \" : \" %s \" , " ) , EscapeJSONString ( friendlyName ) . c_str ( ) ) ;
2020-11-11 11:09:18 +00:00
}
// Add all other attributes
2020-11-13 12:32:45 +00:00
ResponseAppend_P ( PSTR ( " %s}} " ) , attr_list . toString ( false ) . c_str ( ) ) ;
2020-11-11 11:09:18 +00:00
if ( ! Settings . flag4 . remove_zbreceived ) {
ResponseAppend_P ( PSTR ( " } " ) ) ;
}
if ( Settings . flag4 . zigbee_distinct_topics ) {
2020-11-13 12:32:45 +00:00
if ( Settings . flag4 . zb_topic_fname & & friendlyName ) {
2020-11-11 11:09:18 +00:00
//Clean special characters and check size of friendly name
char stemp [ TOPSZ ] ;
2020-11-13 12:32:45 +00:00
strlcpy ( stemp , ( ! strlen ( friendlyName ) ) ? MQTT_TOPIC : friendlyName , sizeof ( stemp ) ) ;
2020-11-11 11:09:18 +00:00
MakeValidMqtt ( 0 , stemp ) ;
//Create topic with Prefix3 and cleaned up friendly name
char frtopic [ TOPSZ ] ;
snprintf_P ( frtopic , sizeof ( frtopic ) , PSTR ( " %s/%s/ " D_RSLT_SENSOR ) , SettingsText ( SET_MQTTPREFIX3 ) , stemp ) ;
MqttPublish ( frtopic , Settings . flag . mqtt_sensor_retain ) ;
} else {
char subtopic [ 16 ] ;
2020-11-13 12:32:45 +00:00
snprintf_P ( subtopic , sizeof ( subtopic ) , PSTR ( " %04X/ " D_RSLT_SENSOR ) , shortaddr ) ;
2020-11-11 11:09:18 +00:00
MqttPublishPrefixTopic_P ( TELE , subtopic , Settings . flag . mqtt_sensor_retain ) ;
}
} else {
MqttPublishPrefixTopic_P ( TELE , PSTR ( D_RSLT_SENSOR ) , Settings . flag . mqtt_sensor_retain ) ;
}
XdrvRulesProcess ( ) ; // apply rules
}
2020-10-07 19:04:33 +01:00
void Z_Devices : : jsonPublishFlush ( uint16_t shortaddr ) {
Z_Device & device = getShortAddr ( shortaddr ) ;
if ( ! device . valid ( ) ) { return ; } // safeguard
Z_attribute_list & attr_list = device . attr_list ;
if ( ! attr_list . isEmpty ( ) ) {
// save parameters is global variables to be used by Rules
gZbLastMessage . device = shortaddr ; // %zbdevice%
gZbLastMessage . groupaddr = attr_list . group_id ; // %zbgroup%
gZbLastMessage . endpoint = attr_list . src_ep ; // %zbendpoint%
2020-11-13 12:32:45 +00:00
device . jsonPublishAttrList ( PSTR ( D_JSON_ZIGBEE_RECEIVED ) , attr_list ) ;
2020-10-07 19:04:33 +01:00
attr_list . reset ( ) ; // clear the attributes
}
}
void Z_Devices : : jsonPublishNow ( uint16_t shortaddr , Z_attribute_list & attr_list ) {
jsonPublishFlush ( shortaddr ) ; // flush any previous buffer
jsonAppend ( shortaddr , attr_list ) ;
jsonPublishFlush ( shortaddr ) ; // publish now
}
void Z_Devices : : dirty ( void ) {
_saveTimer = kZigbeeSaveDelaySeconds * 1000 + millis ( ) ;
}
void Z_Devices : : clean ( void ) {
_saveTimer = 0 ;
}
// Parse the command parameters for either:
// - a short address starting with "0x", example: 0x1234
// - a long address starting with "0x", example: 0x7CB03EBB0A0292DD
// - a number 0..99, the index number in ZigbeeStatus
// - a friendly name, between quotes, example: "Room_Temp"
2020-12-08 18:21:32 +00:00
//
// In case the device is not found, the parsed 0x.... short address is passed to *parsed_shortaddr
Z_Device & Z_Devices : : parseDeviceFromName ( const char * param , uint16_t * parsed_shortaddr ) {
2020-11-01 18:00:07 +00:00
if ( nullptr = = param ) { return device_unk ; }
2020-10-07 19:04:33 +01:00
size_t param_len = strlen ( param ) ;
char dataBuf [ param_len + 1 ] ;
strcpy ( dataBuf , param ) ;
RemoveSpace ( dataBuf ) ;
2020-12-08 18:21:32 +00:00
if ( parsed_shortaddr ! = nullptr ) { * parsed_shortaddr = BAD_SHORTADDR ; } // if it goes wrong, mark as bad
2020-10-07 19:04:33 +01:00
2020-11-01 18:00:07 +00:00
if ( ( dataBuf [ 0 ] > = ' 0 ' ) & & ( dataBuf [ 0 ] < = ' 9 ' ) & & ( strlen ( dataBuf ) < 4 ) ) {
2020-10-07 19:04:33 +01:00
// simple number 0..99
if ( ( XdrvMailbox . payload > 0 ) & & ( XdrvMailbox . payload < = 99 ) ) {
2020-11-01 18:00:07 +00:00
return isKnownIndexDevice ( XdrvMailbox . payload - 1 ) ;
} else {
return device_unk ;
2020-10-07 19:04:33 +01:00
}
} else if ( ( dataBuf [ 0 ] = = ' 0 ' ) & & ( ( dataBuf [ 1 ] = = ' x ' ) | | ( dataBuf [ 1 ] = = ' X ' ) ) ) {
// starts with 0x
if ( strlen ( dataBuf ) < 18 ) {
// expect a short address
2020-11-01 18:00:07 +00:00
uint16_t shortaddr = strtoull ( dataBuf , nullptr , 0 ) ;
2020-12-08 18:21:32 +00:00
if ( parsed_shortaddr ! = nullptr ) { * parsed_shortaddr = shortaddr ; } // return the parsed shortaddr even if the device doesn't exist
return ( Z_Device & ) findShortAddr ( shortaddr ) ; // if not found, it reverts to the unknown_device with address BAD_SHORTADDR
2020-10-07 19:04:33 +01:00
} else {
// expect a long address
uint64_t longaddr = strtoull ( dataBuf , nullptr , 0 ) ;
2020-11-01 18:00:07 +00:00
return isKnownLongAddrDevice ( longaddr ) ;
2020-10-07 19:04:33 +01:00
}
} else {
// expect a Friendly Name
2020-11-01 18:00:07 +00:00
return isKnownFriendlyNameDevice ( dataBuf ) ;
2020-10-07 19:04:33 +01:00
}
}
2020-11-13 12:32:45 +00:00
/*********************************************************************************************\
*
* Methods below build a JSON representation of device data
* Used by : ZbLight , ZbStatus , ZbInfo
*
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-10-07 19:04:33 +01:00
2020-11-13 12:32:45 +00:00
// Add "Device":"0x1234","Name":"FrienflyName"
void Z_Device : : jsonAddDeviceNamme ( Z_attribute_list & attr_list ) const {
const char * fname = friendlyName ;
2020-10-07 19:04:33 +01:00
bool use_fname = ( Settings . flag4 . zigbee_use_names ) & & ( fname ) ; // should we replace shortaddr with friendlyname?
2020-12-08 18:21:32 +00:00
attr_list . addAttributePMEM ( PSTR ( D_JSON_ZIGBEE_DEVICE ) ) . setHex32 ( shortaddr ) ;
2020-10-07 19:04:33 +01:00
if ( fname ) {
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( D_JSON_ZIGBEE_NAME ) ) . setStr ( fname ) ;
2020-10-07 19:04:33 +01:00
}
2020-11-13 12:32:45 +00:00
}
2020-11-30 18:25:05 +00:00
2020-11-13 12:32:45 +00:00
// Add "IEEEAddr":"0x1234567812345678"
void Z_Device : : jsonAddIEEE ( Z_attribute_list & attr_list ) const {
2020-12-08 18:21:32 +00:00
attr_list . addAttributePMEM ( PSTR ( " IEEEAddr " ) ) . setHex64 ( longaddr ) ;
2020-11-13 12:32:45 +00:00
}
// Add "ModelId":"","Manufacturer":""
void Z_Device : : jsonAddModelManuf ( Z_attribute_list & attr_list ) const {
if ( modelId ) {
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( D_JSON_MODEL D_JSON_ID ) ) . setStr ( modelId ) ;
2020-11-13 12:32:45 +00:00
}
if ( manufacturerId ) {
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Manufacturer " ) ) . setStr ( manufacturerId ) ;
2020-11-13 12:32:45 +00:00
}
}
2020-11-30 18:25:05 +00:00
2020-11-13 12:32:45 +00:00
// Add "Endpoints":[...]
void Z_Device : : jsonAddEndpoints ( Z_attribute_list & attr_list ) const {
JsonGeneratorArray arr_ep ;
for ( uint32_t i = 0 ; i < endpoints_max ; i + + ) {
uint8_t endpoint = endpoints [ i ] ;
if ( 0x00 = = endpoint ) { break ; }
arr_ep . add ( endpoint ) ;
}
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Endpoints " ) ) . setStrRaw ( arr_ep . toString ( ) . c_str ( ) ) ;
2020-11-13 12:32:45 +00:00
}
// Add "Config":["",""...]
void Z_Device : : jsonAddConfig ( Z_attribute_list & attr_list ) const {
JsonGeneratorArray arr_data ;
for ( auto & data_elt : data ) {
char key [ 8 ] ;
if ( data_elt . validConfig ( ) ) {
snprintf_P ( key , sizeof ( key ) , " ?%02X.%1X " , data_elt . getEndpoint ( ) , data_elt . getConfig ( ) ) ;
} else {
snprintf_P ( key , sizeof ( key ) , " ?%02X " , data_elt . getEndpoint ( ) ) ;
}
key [ 0 ] = Z_Data : : DataTypeToChar ( data_elt . getType ( ) ) ;
arr_data . addStr ( key ) ;
}
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Config " ) ) . setStrRaw ( arr_data . toString ( ) . c_str ( ) ) ;
2020-11-13 12:32:45 +00:00
}
// Add All data attributes
void Z_Device : : jsonAddDataAttributes ( Z_attribute_list & attr_list ) const {
// show internal data - mostly last known values
for ( auto & data_elt : data ) {
data_elt . toAttributes ( attr_list ) ;
}
}
// Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality"
void Z_Device : : jsonAddDeviceAttributes ( Z_attribute_list & attr_list ) const {
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Reachable " ) ) . setBool ( getReachable ( ) ) ;
if ( validBatteryPercent ( ) ) { attr_list . addAttributePMEM ( PSTR ( " BatteryPercentage " ) ) . setUInt ( batterypercent ) ; }
2020-11-13 12:32:45 +00:00
if ( validLastSeen ( ) ) {
if ( Rtc . utc_time > = last_seen ) {
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " LastSeen " ) ) . setUInt ( Rtc . utc_time - last_seen ) ;
2020-11-13 12:32:45 +00:00
}
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " LastSeenEpoch " ) ) . setUInt ( last_seen ) ;
2020-11-13 12:32:45 +00:00
}
2020-11-30 18:25:05 +00:00
if ( validLqi ( ) ) { attr_list . addAttributePMEM ( PSTR ( D_CMND_ZIGBEE_LINKQUALITY ) ) . setUInt ( lqi ) ; }
2020-11-13 12:32:45 +00:00
}
2020-10-07 19:04:33 +01:00
2020-11-13 12:32:45 +00:00
// Display the tracked status for a light
void Z_Device : : jsonLightState ( Z_attribute_list & attr_list ) const {
if ( valid ( ) ) {
2020-10-07 19:04:33 +01:00
// dump all known values
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Reachable " ) ) . setBool ( getReachable ( ) ) ;
if ( validPower ( ) ) { attr_list . addAttributePMEM ( PSTR ( " Power " ) ) . setUInt ( getPower ( ) ) ; }
2020-11-14 10:23:43 +00:00
int32_t light_mode = - 1 ;
2020-11-13 12:32:45 +00:00
const Z_Data_Light & light = data . find < Z_Data_Light > ( 0 ) ;
2020-10-09 18:10:36 +01:00
if ( & light ! = nullptr ) {
2020-11-14 10:23:43 +00:00
if ( light . validConfig ( ) ) {
light_mode = light . getConfig ( ) ;
}
2020-11-13 12:32:45 +00:00
light . toAttributes ( attr_list ) ;
2020-10-09 18:10:36 +01:00
// Exception, we need to convert Hue to 0..360 instead of 0..254
if ( light . validHue ( ) ) {
attr_list . findOrCreateAttribute ( PSTR ( " Hue " ) ) . setUInt ( light . getHue ( ) ) ;
}
}
2020-11-30 18:25:05 +00:00
attr_list . addAttributePMEM ( PSTR ( " Light " ) ) . setInt ( light_mode ) ;
2020-10-07 19:04:33 +01:00
}
}
2020-11-13 12:32:45 +00:00
// Dump the internal memory of Zigbee devices - does not include "Device" and "Name"
2020-10-07 19:04:33 +01:00
// Mode = 1: simple dump of devices addresses
// Mode = 2: simple dump of devices addresses and names, endpoints, light
2020-11-11 11:09:18 +00:00
// Mode = 3: dump last known data attributes
2020-11-13 12:32:45 +00:00
// String Z_Device::dumpSingleDevice(uint32_t dump_mode, bool add_device_name, bool add_brackets) const {
void Z_Device : : jsonDumpSingleDevice ( Z_attribute_list & attr_list , uint32_t dump_mode , bool add_name ) const {
if ( add_name ) {
jsonAddDeviceNamme ( attr_list ) ;
2020-11-01 18:00:07 +00:00
}
2020-11-11 11:09:18 +00:00
if ( dump_mode > = 2 ) {
2020-11-13 12:32:45 +00:00
jsonAddIEEE ( attr_list ) ;
jsonAddModelManuf ( attr_list ) ;
jsonAddEndpoints ( attr_list ) ;
jsonAddConfig ( attr_list ) ;
2020-11-01 18:00:07 +00:00
}
2020-11-11 11:09:18 +00:00
if ( dump_mode > = 3 ) {
2020-11-13 12:32:45 +00:00
jsonAddDataAttributes ( attr_list ) ;
2020-11-11 11:09:18 +00:00
// add device wide attributes
2020-11-13 12:32:45 +00:00
jsonAddDeviceAttributes ( attr_list ) ;
2020-11-11 11:09:18 +00:00
}
2020-11-01 18:00:07 +00:00
}
2020-10-28 09:08:15 +00:00
2020-12-08 18:21:32 +00:00
// Dump coordinator specific data
String Z_Devices : : dumpCoordinator ( void ) const {
Z_attribute_list attr_list ;
attr_list . addAttributePMEM ( PSTR ( D_JSON_ZIGBEE_DEVICE ) ) . setHex32 ( localShortAddr ) ;
attr_list . addAttributePMEM ( PSTR ( " IEEEAddr " ) ) . setHex64 ( localIEEEAddr ) ;
attr_list . addAttributePMEM ( PSTR ( " TotalDevices " ) ) . setUInt ( zigbee_devices . devicesSize ( ) ) ;
return attr_list . toString ( ) ;
}
2020-11-01 18:00:07 +00:00
// If &device == nullptr, then dump all
String Z_Devices : : dumpDevice ( uint32_t dump_mode , const Z_Device & device ) const {
JsonGeneratorArray json_arr ;
if ( & device = = nullptr ) {
if ( dump_mode < 2 ) {
// dump light mode for all devices
for ( const auto & device2 : _devices ) {
2020-11-13 12:32:45 +00:00
Z_attribute_list attr_list ;
device2 . jsonDumpSingleDevice ( attr_list , dump_mode , true ) ;
json_arr . addStrRaw ( attr_list . toString ( true ) . c_str ( ) ) ;
2020-10-28 09:08:15 +00:00
}
2020-10-07 19:04:33 +01:00
}
2020-11-01 18:00:07 +00:00
} else {
2020-11-13 12:32:45 +00:00
Z_attribute_list attr_list ;
device . jsonDumpSingleDevice ( attr_list , dump_mode , true ) ;
json_arr . addStrRaw ( attr_list . toString ( true ) . c_str ( ) ) ;
2020-10-07 19:04:33 +01:00
}
2020-11-01 18:00:07 +00:00
2020-10-07 19:04:33 +01:00
return json_arr . toString ( ) ;
}
// Restore a single device configuration based on json export
// Input: json element as expported by `ZbStatus2``
// Mandatory attribue: `Device`
//
// Returns:
// 0 : Ok
// <0 : Error
//
// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
int32_t Z_Devices : : deviceRestore ( JsonParserObject json ) {
// params
2020-10-28 09:08:15 +00:00
uint16_t shortaddr = 0x0000 ; // 0x0000 is coordinator so considered invalid
2020-10-07 19:04:33 +01:00
uint64_t ieeeaddr = 0x0000000000000000LL ; // 0 means unknown
const char * modelid = nullptr ;
const char * manufid = nullptr ;
const char * friendlyname = nullptr ;
// read mandatory "Device"
JsonParserToken val_device = json [ PSTR ( " Device " ) ] ;
if ( val_device ) {
2020-10-28 09:08:15 +00:00
shortaddr = ( uint32_t ) val_device . getUInt ( shortaddr ) ;
2020-10-07 19:04:33 +01:00
} else {
return - 1 ; // missing "Device" attribute
}
ieeeaddr = json . getULong ( PSTR ( " IEEEAddr " ) , ieeeaddr ) ; // read "IEEEAddr" 64 bits in format "0x0000000000000000"
friendlyname = json . getStr ( PSTR ( " Name " ) , nullptr ) ; // read "Name"
modelid = json . getStr ( PSTR ( " ModelId " ) , nullptr ) ;
manufid = json . getStr ( PSTR ( " Manufacturer " ) , nullptr ) ;
// update internal device information
2020-10-28 09:08:15 +00:00
updateDevice ( shortaddr , ieeeaddr ) ;
2020-10-31 18:51:17 +00:00
Z_Device & device = getShortAddr ( shortaddr ) ;
if ( modelid ) { device . setModelId ( modelid ) ; }
if ( manufid ) { device . setManufId ( manufid ) ; }
if ( friendlyname ) { device . setFriendlyName ( friendlyname ) ; }
2020-10-07 19:04:33 +01:00
// read "Endpoints"
JsonParserToken val_endpoints = json [ PSTR ( " Endpoints " ) ] ;
if ( val_endpoints . isArray ( ) ) {
JsonParserArray arr_ep = JsonParserArray ( val_endpoints ) ;
2020-10-31 18:51:17 +00:00
device . clearEndpoints ( ) ; // clear even if array is empty
2020-10-07 19:04:33 +01:00
for ( auto ep_elt : arr_ep ) {
uint8_t ep = ep_elt . getUInt ( ) ;
2020-10-31 18:51:17 +00:00
if ( ep ) { device . addEndpoint ( ep ) ; }
2020-10-28 09:08:15 +00:00
}
}
// read "Config"
JsonParserToken val_config = json [ PSTR ( " Config " ) ] ;
if ( val_config . isArray ( ) ) {
JsonParserArray arr_config = JsonParserArray ( val_config ) ;
device . data . reset ( ) ; // remove existing configuration
for ( auto config_elt : arr_config ) {
const char * conf_str = config_elt . getStr ( ) ;
Z_Data_Type data_type ;
2020-12-06 18:20:42 +00:00
uint8_t ep = 0 ;
uint8_t config = 0xF ; // default = no config
2020-10-28 09:08:15 +00:00
if ( Z_Data : : ConfigToZData ( conf_str , & data_type , & ep , & config ) ) {
Z_Data & data = device . data . getByType ( data_type , ep ) ;
if ( & data ! = nullptr ) {
data . setConfig ( config ) ;
}
2020-12-06 18:20:42 +00:00
} else {
AddLog_P ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Ignoring config '%s' " ) , conf_str ) ;
2020-10-28 09:08:15 +00:00
}
2020-10-07 19:04:33 +01:00
}
}
return 0 ;
}
Z_Data_Light & Z_Devices : : getLight ( uint16_t shortaddr ) {
return getShortAddr ( shortaddr ) . data . get < Z_Data_Light > ( ) ;
}
2020-12-06 18:20:42 +00:00
bool Z_Devices : : isTuyaProtocol ( uint16_t shortaddr , uint8_t ep ) const {
const Z_Device & device = findShortAddr ( shortaddr ) ;
if ( device . valid ( ) ) {
const Z_Data_Mode & mode = device . data . getConst < Z_Data_Mode > ( ep ) ;
if ( & mode ! = nullptr ) {
return mode . isTuyaProtocol ( ) ;
}
}
return false ;
}
2020-10-07 19:04:33 +01:00
/*********************************************************************************************\
* Device specific data handlers
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Z_Device : : setPower ( bool power_on , uint8_t ep ) {
data . get < Z_Data_OnOff > ( ep ) . setPower ( power_on ) ;
}
bool Z_Device : : validPower ( uint8_t ep ) const {
const Z_Data_OnOff & onoff = data . find < Z_Data_OnOff > ( ep ) ;
return ( & onoff ! = nullptr ) ;
}
bool Z_Device : : getPower ( uint8_t ep ) const {
const Z_Data_OnOff & onoff = data . find < Z_Data_OnOff > ( ep ) ;
if ( & onoff ! = nullptr ) return onoff . getPower ( ) ;
return false ;
}
# endif // USE_ZIGBEE