2020-01-17 23:02:01 +00:00
/*
xdrv_23_zigbee . ino - zigbee support for Tasmota
Copyright ( C ) 2020 Theo Arends and Stephan Hadinger
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_ZIGBEE
// Ensure persistence of devices into Flash
//
// Structure:
// (from file info):
// uint16 - start address in Flash (offset)
// uint16 - length in bytes (makes sure parsing stops)
//
2020-10-28 09:08:15 +00:00
// First byte:
// 0x00 - Empty or V3 format
// 0x01-0xFE - Legacy format
// 0xFF - invalid
//
//
// V1 Legacy
// =========
2020-01-17 23:02:01 +00:00
// File structure:
// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased)
//
// [Array of devices]
// [Offset = 2]
2020-02-12 21:38:17 +00:00
// uint8 - length of device record
2020-01-17 23:02:01 +00:00
// uint16 - short address
// uint64 - long IEEE address
// uint8 - number of endpoints
// [Array of endpoints]
// uint8 - endpoint number
// uint16 - profileID of the endpoint
// Array of uint8 - clusters In codes, 0xFF end marker
// Array of uint8 - clusters Out codes, 0xFF end marker
//
// str - ModelID (null terminated C string, 32 chars max)
// str - Manuf (null terminated C string, 32 chars max)
2020-02-12 21:38:17 +00:00
// str - FriendlyName (null terminated C string, 32 chars max)
2020-01-17 23:02:01 +00:00
// reserved for extensions
2020-04-22 15:07:52 +01:00
// -- V2 --
2020-08-20 07:25:53 +01:00
// int8_t - zigbee profile of the device
2020-10-28 09:08:15 +00:00
//
// =======================
// v3 with version number
// File structure:
//
// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased)
//
// [Array of devices]
// [Offset = 2]
// uint8 - length of device record
// uint16 - short address
// uint64 - long IEEE address
//
// str - ModelID (null terminated C string, 32 chars max)
// str - Manuf (null terminated C string, 32 chars max)
// str - FriendlyName (null terminated C string, 32 chars max)
//
// [Array of endpoints]
// uint8 - endpoint number, 0xFF marks the end of endpoints
// uint8[] - list of configuration bytes, 0xFF marks the end
// i.e. 0xFF-0xFF marks the end of the array of endpoints
//
2020-01-17 23:02:01 +00:00
// Memory footprint
2020-07-05 13:51:55 +01:00
# ifdef ESP8266
2020-01-17 23:02:01 +00:00
const static uint16_t z_spi_start_sector = 0xFF ; // Force last bank of first MB
const static uint8_t * z_spi_start = ( uint8_t * ) 0x402FF000 ; // 0x402FF000
2020-07-05 13:51:55 +01:00
const static uint8_t * z_dev_start = z_spi_start + 0x0800 ; // 0x402FF800 - 2KB
const static size_t z_spi_len = 0x1000 ; // 4kb blocks
2020-01-17 23:02:01 +00:00
const static size_t z_block_offset = 0x0800 ;
const static size_t z_block_len = 0x0800 ; // 2kb
2020-07-05 13:51:55 +01:00
# else // ESP32
uint8_t * z_dev_start ;
const static size_t z_spi_len = 0x1000 ; // 4kb blocks
const static size_t z_block_offset = 0x0000 ; // No offset needed
const static size_t z_block_len = 0x1000 ; // 4kb
# endif
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
// Each entry consumes 8 bytes
class Z_Flashentry {
2020-01-17 23:02:01 +00:00
public :
2020-10-28 09:08:15 +00:00
uint32_t name ; // simple 4 letters name. Currently 'zig1', 'zig2'. 0xFFFFFFFF if not entry
uint16_t len ; // len of object in bytes, 0xFFFF if no entry
uint16_t start ; // address of start, 0xFFFF if empty, must be aligned on 128 bytes boundaries
} ;
class Z_Flashdirectory {
public :
// 8 bytes header
uint32_t magic ; // magic value 'Tsmt' to check that the block is initialized
uint32_t clock ; // clock vector to discard entries that are made before this one. This should be incremented by 1 for each new entry (future anti-weavering)
// entries, 14*8 = 112 bytes
Z_Flashentry entries [ 14 ] ;
2020-01-17 23:02:01 +00:00
uint32_t name ; // simple 4 letters name. Currently 'skey', 'crt ', 'crt1', 'crt2'
uint16_t len ; // len of object
uint16_t reserved ; // align on 4 bytes boundary
2020-10-28 09:08:15 +00:00
// link to next entry, none for now, but may be used for anti-weavering
uint16_t next_dir ; // 0xFFFF if none
uint16_t reserved1 ; // must be 0xFFFF
uint32_t reserved2 ; // must be 0xFFFFFFFF
2020-04-22 15:07:52 +01:00
} ;
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
const static uint32_t ZIGB_NAME1 = 0x3167697A ; // 'zig1' little endian
const static uint32_t ZIGB_NAME2 = 0x3267697A ; // 'zig2' little endian, v2
const static size_t Z_MAX_FLASH = z_block_len - sizeof ( Z_Flashentry ) ; // 2040
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
bool hibernateDeviceConfiguration ( SBuffer & buf , const class Z_Data_Set & data , uint8_t endpoint ) {
bool found = false ;
for ( auto & elt : data ) {
if ( endpoint = = elt . getEndpoint ( ) ) {
buf . add8 ( elt . getConfigByte ( ) ) ;
found = true ;
}
}
return found ;
}
class SBuffer hibernateDevicev2 ( const struct Z_Device & device ) {
2020-01-17 23:02:01 +00:00
SBuffer buf ( 128 ) ;
buf . add8 ( 0x00 ) ; // overall length, will be updated later
buf . add16 ( device . shortaddr ) ;
buf . add64 ( device . longaddr ) ;
2020-03-17 17:46:05 +00:00
2020-10-28 09:08:15 +00:00
char * names [ 3 ] = { device . modelId , device . manufacturerId , device . friendlyName } ;
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
for ( uint32_t i = 0 ; i < ARRAY_SIZE ( names ) ; i + + ) {
char * p = names [ i ] ;
if ( p ) {
size_t len = strlen ( p ) ;
if ( len > 32 ) { len = 32 ; } // max 32 chars
buf . addBuffer ( p , len ) ;
}
buf . add8 ( 0x00 ) ; // end of string marker
2020-03-14 13:17:30 +00:00
}
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
// check if we need to write fake endpoint 0x00
buf . add8 ( 0x00 ) ;
if ( hibernateDeviceConfiguration ( buf , device . data , 0 ) ) {
buf . add8 ( 0xFF ) ; // end of configuration
} else {
buf . setLen ( buf . len ( ) - 1 ) ; // remove 1 byte header
2020-03-14 13:17:30 +00:00
}
2020-10-28 09:08:15 +00:00
// scan endpoints
for ( uint32_t i = 0 ; i < endpoints_max ; i + + ) {
uint8_t endpoint = device . endpoints [ i ] ;
if ( 0x00 = = endpoint ) { break ; }
buf . add8 ( endpoint ) ;
hibernateDeviceConfiguration ( buf , device . data , endpoint ) ;
buf . add8 ( 0xFF ) ; // end of configuration
2020-03-14 13:17:30 +00:00
}
2020-10-28 09:08:15 +00:00
buf . add8 ( 0xFF ) ; // end of endpoints
2020-03-14 13:17:30 +00:00
2020-01-17 23:02:01 +00:00
// update overall length
buf . set8 ( 0 , buf . len ( ) ) ;
return buf ;
}
class SBuffer hibernateDevices ( void ) {
SBuffer buf ( 2048 ) ;
size_t devices_size = zigbee_devices . devicesSize ( ) ;
if ( devices_size > 32 ) { devices_size = 32 ; } // arbitrarily limit to 32 devices, for now
buf . add8 ( devices_size ) ; // number of devices
for ( uint32_t i = 0 ; i < devices_size ; i + + ) {
const Z_Device & device = zigbee_devices . devicesAt ( i ) ;
2020-10-28 09:08:15 +00:00
const SBuffer buf_device = hibernateDevicev2 ( device ) ;
2020-01-17 23:02:01 +00:00
buf . addBuffer ( buf_device ) ;
}
size_t buf_len = buf . len ( ) ;
if ( buf_len > 2040 ) {
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( D_LOG_ZIGBEE " Devices list too big to fit in Flash (%d) " ) , buf_len ) ;
}
// Log
char * hex_char = ( char * ) malloc ( ( buf_len * 2 ) + 2 ) ;
if ( hex_char ) {
2020-01-25 16:42:53 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_ZIGBEE " ZbFlashStore %s " ) ,
2020-01-17 23:02:01 +00:00
ToHex_P ( buf . getBuffer ( ) , buf_len , hex_char , ( buf_len * 2 ) + 2 ) ) ;
free ( hex_char ) ;
}
return buf ;
}
2020-10-28 09:08:15 +00:00
// parse a single string from the saved data
// if something wrong happens, returns nullptr to ignore the string
// Index d is incremented to just after the string
const char * hydrateSingleString ( const SBuffer & buf , uint32_t * d ) {
size_t s_len = buf . strlen ( * d ) ;
const char * ptr = s_len ? buf . charptr ( * d ) : " " ;
* d + = s_len + 1 ;
return ptr ;
}
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
void hydrateSingleDevice ( const SBuffer & buf_d , uint32_t version ) {
uint32_t d = 1 ; // index in device buffer
uint16_t shortaddr = buf_d . get16 ( d ) ; d + = 2 ;
uint64_t longaddr = buf_d . get64 ( d ) ; d + = 8 ;
size_t buf_len = buf_d . len ( ) ;
Z_Device & device = zigbee_devices . updateDevice ( shortaddr , longaddr ) ; // update device's addresses
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
if ( 1 = = version ) {
2020-01-17 23:02:01 +00:00
uint32_t endpoints = buf_d . get8 ( d + + ) ;
for ( uint32_t j = 0 ; j < endpoints ; j + + ) {
uint8_t ep = buf_d . get8 ( d + + ) ;
uint16_t ep_profile = buf_d . get16 ( d ) ; d + = 2 ;
2020-03-17 17:46:05 +00:00
zigbee_devices . addEndpoint ( shortaddr , ep ) ;
2020-01-17 23:02:01 +00:00
// in clusters
2020-10-28 09:08:15 +00:00
while ( d < buf_len ) { // safe guard against overflow
2020-01-17 23:02:01 +00:00
uint8_t ep_cluster = buf_d . get8 ( d + + ) ;
if ( 0xFF = = ep_cluster ) { break ; } // end of block
2020-03-17 17:46:05 +00:00
// ignore
2020-01-17 23:02:01 +00:00
}
// out clusters
2020-10-28 09:08:15 +00:00
while ( d < buf_len ) { // safe guard against overflow
2020-01-17 23:02:01 +00:00
uint8_t ep_cluster = buf_d . get8 ( d + + ) ;
if ( 0xFF = = ep_cluster ) { break ; } // end of block
2020-03-17 17:46:05 +00:00
// ignore
2020-01-17 23:02:01 +00:00
}
}
2020-10-28 09:08:15 +00:00
}
// ModelId
zigbee_devices . setModelId ( shortaddr , hydrateSingleString ( buf_d , & d ) ) ;
// ManufID
zigbee_devices . setManufId ( shortaddr , hydrateSingleString ( buf_d , & d ) ) ;
// FriendlyName
zigbee_devices . setFriendlyName ( shortaddr , hydrateSingleString ( buf_d , & d ) ) ;
2020-03-14 13:17:30 +00:00
2020-10-28 09:08:15 +00:00
if ( d > = buf_len ) { return ; }
// Hue bulbtype - if present
if ( 1 = = version ) {
zigbee_devices . setLightProfile ( shortaddr , buf_d . get8 ( d ) ) ;
d + + ;
} else if ( 2 = = version ) {
// v2 parser
while ( d < buf_len ) {
uint8_t ep = buf_d . get8 ( d + + ) ;
if ( 0xFF = = ep ) { break ; } // ep 0xFF marks the end of the endpoints
if ( ep > 240 ) { ep = 0xFF ; } // ep == 0xFF means ignore
if ( ( ep > 0 ) & & ( ep ! = 0xFF ) ) { zigbee_devices . addEndpoint ( shortaddr , ep ) ; } // don't add endpoint if it is 0x00
while ( d < buf_len ) {
uint8_t config_type = buf_d . get8 ( d + + ) ;
if ( 0xFF = = config_type ) { break ; } // 0xFF marks the end of congiguration
uint8_t config = config_type & 0x0F ;
Z_Data_Type type = ( Z_Data_Type ) ( config_type > > 4 ) ;
// set the configuration
if ( ep ! = 0xFF ) {
Z_Data & z_data = device . data . getByType ( type , ep ) ;
if ( & z_data ! = nullptr ) {
z_data . setConfig ( config ) ;
}
}
}
2020-03-14 13:17:30 +00:00
}
2020-10-28 09:08:15 +00:00
}
}
void hydrateDevices ( const SBuffer & buf , uint32_t version ) {
uint32_t buf_len = buf . len ( ) ;
if ( buf_len < = 10 ) { return ; }
uint32_t k = 0 ; // byte index in global buffer
uint32_t num_devices = buf . get8 ( k + + ) ;
for ( uint32_t i = 0 ; ( i < num_devices ) & & ( k < buf_len ) ; i + + ) {
uint32_t dev_record_len = buf . get8 ( k ) ;
SBuffer buf_d = buf . subBuffer ( k , dev_record_len ) ;
hydrateSingleDevice ( buf_d , version ) ;
2020-03-14 13:17:30 +00:00
2020-01-17 23:02:01 +00:00
// next iteration
k + = dev_record_len ;
}
}
2020-10-28 09:08:15 +00:00
2020-01-17 23:02:01 +00:00
void loadZigbeeDevices ( void ) {
2020-07-05 13:51:55 +01:00
# ifdef ESP32
// first copy SPI buffer into ram
uint8_t * spi_buffer = ( uint8_t * ) malloc ( z_spi_len ) ;
if ( ! spi_buffer ) {
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( D_LOG_ZIGBEE " Cannot allocate 4KB buffer " ) ) ;
return ;
}
ZigbeeRead ( & spi_buffer , z_spi_len ) ;
z_dev_start = spi_buffer ;
# endif // ESP32
2020-10-28 09:08:15 +00:00
Z_Flashentry flashdata ;
memcpy_P ( & flashdata , z_dev_start , sizeof ( Z_Flashentry ) ) ;
2020-04-22 15:07:52 +01:00
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
2020-01-17 23:02:01 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_ZIGBEE " Zigbee signature in Flash: %08X - %d " ) , flashdata . name , flashdata . len ) ;
// Check the signature
2020-10-28 09:08:15 +00:00
if ( ( ( flashdata . name = = ZIGB_NAME1 ) | | ( flashdata . name = = ZIGB_NAME2 ) )
& & ( flashdata . len > 0 ) ) {
2020-01-17 23:02:01 +00:00
uint16_t buf_len = flashdata . len ;
2020-10-28 09:08:15 +00:00
uint32_t version = ( flashdata . name = = ZIGB_NAME2 ) ? 2 : 1 ;
2020-01-17 23:02:01 +00:00
// parse what seems to be a valid entry
SBuffer buf ( buf_len ) ;
2020-10-28 09:08:15 +00:00
buf . addBuffer ( z_dev_start + sizeof ( Z_Flashentry ) , buf_len ) ;
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Zigbee devices data in Flash v%d (%d bytes) " ) , version , buf_len ) ;
// Serial.printf(">> Buffer=");
// for (uint32_t i=0; i<buf.len(); i++) Serial.printf("%02X ", buf.get8(i));
// Serial.printf("\n");
hydrateDevices ( buf , version ) ;
2020-01-22 21:40:28 +00:00
zigbee_devices . clean ( ) ; // don't write back to Flash what we just loaded
2020-01-17 23:02:01 +00:00
} else {
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " No zigbee devices data in Flash " ) ) ;
}
2020-04-22 15:07:52 +01:00
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
2020-07-05 13:51:55 +01:00
# ifdef ESP32
free ( spi_buffer ) ;
2020-07-05 14:13:57 +01:00
# endif // ESP32
2020-01-17 23:02:01 +00:00
}
void saveZigbeeDevices ( void ) {
SBuffer buf = hibernateDevices ( ) ;
size_t buf_len = buf . len ( ) ;
if ( buf_len > Z_MAX_FLASH ) {
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( D_LOG_ZIGBEE " Buffer too big to fit in Flash (%d bytes) " ) , buf_len ) ;
return ;
}
// first copy SPI buffer into ram
uint8_t * spi_buffer = ( uint8_t * ) malloc ( z_spi_len ) ;
if ( ! spi_buffer ) {
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( D_LOG_ZIGBEE " Cannot allocate 4KB buffer " ) ) ;
return ;
}
// copy the flash into RAM to make local change, and write back the whole buffer
2020-07-05 13:51:55 +01:00
# ifdef ESP8266
2020-01-17 23:02:01 +00:00
ESP . flashRead ( z_spi_start_sector * SPI_FLASH_SEC_SIZE , ( uint32_t * ) spi_buffer , SPI_FLASH_SEC_SIZE ) ;
2020-07-05 13:51:55 +01:00
# else // ESP32
ZigbeeRead ( & spi_buffer , z_spi_len ) ;
2020-07-05 14:13:57 +01:00
# endif // ESP8266 - ESP32
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
Z_Flashentry * flashdata = ( Z_Flashentry * ) ( spi_buffer + z_block_offset ) ;
flashdata - > name = ZIGB_NAME2 ; // v2
2020-01-17 23:02:01 +00:00
flashdata - > len = buf_len ;
2020-10-28 09:08:15 +00:00
flashdata - > start = 0 ;
2020-01-17 23:02:01 +00:00
2020-10-28 09:08:15 +00:00
memcpy ( spi_buffer + z_block_offset + sizeof ( Z_Flashentry ) , buf . getBuffer ( ) , buf_len ) ;
2020-01-17 23:02:01 +00:00
// buffer is now ready, write it back
2020-07-05 13:51:55 +01:00
# ifdef ESP8266
2020-01-17 23:02:01 +00:00
if ( ESP . flashEraseSector ( z_spi_start_sector ) ) {
ESP . flashWrite ( z_spi_start_sector * SPI_FLASH_SEC_SIZE , ( uint32_t * ) spi_buffer , SPI_FLASH_SEC_SIZE ) ;
}
2020-07-05 13:51:55 +01:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Zigbee Devices Data store in Flash (0x%08X - %d bytes) " ) , z_dev_start , buf_len ) ;
# else // ESP32
ZigbeeWrite ( & spi_buffer , z_spi_len ) ;
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Zigbee Devices Data saved (%d bytes) " ) , buf_len ) ;
2020-07-05 14:13:57 +01:00
# endif // ESP8266 - ESP32
2020-01-17 23:02:01 +00:00
free ( spi_buffer ) ;
}
// Erase the flash area containing the ZigbeeData
void eraseZigbeeDevices ( void ) {
2020-01-22 21:40:28 +00:00
zigbee_devices . clean ( ) ; // avoid writing data to flash after erase
2020-07-05 14:13:57 +01:00
# ifdef ESP8266
2020-01-17 23:02:01 +00:00
// first copy SPI buffer into ram
uint8_t * spi_buffer = ( uint8_t * ) malloc ( z_spi_len ) ;
if ( ! spi_buffer ) {
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( D_LOG_ZIGBEE " Cannot allocate 4KB buffer " ) ) ;
return ;
}
// copy the flash into RAM to make local change, and write back the whole buffer
ESP . flashRead ( z_spi_start_sector * SPI_FLASH_SEC_SIZE , ( uint32_t * ) spi_buffer , SPI_FLASH_SEC_SIZE ) ;
// Fill the Zigbee area with 0xFF
memset ( spi_buffer + z_block_offset , 0xFF , z_block_len ) ;
2020-04-22 15:07:52 +01:00
2020-01-17 23:02:01 +00:00
// buffer is now ready, write it back
if ( ESP . flashEraseSector ( z_spi_start_sector ) ) {
ESP . flashWrite ( z_spi_start_sector * SPI_FLASH_SEC_SIZE , ( uint32_t * ) spi_buffer , SPI_FLASH_SEC_SIZE ) ;
}
2020-07-05 14:13:57 +01:00
free ( spi_buffer ) ;
2020-07-05 13:51:55 +01:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Zigbee Devices Data erased (0x%08X - %d bytes) " ) , z_dev_start , z_block_len ) ;
# else // ESP32
2020-07-05 14:13:57 +01:00
ZigbeeErase ( ) ;
2020-07-05 13:51:55 +01:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( D_LOG_ZIGBEE " Zigbee Devices Data erased (%d bytes) " ) , z_block_len ) ;
2020-07-05 14:13:57 +01:00
# endif // ESP8266 - ESP32
2020-01-17 23:02:01 +00:00
}
# endif // USE_ZIGBEE