2019-09-15 10:10:59 +01:00
/*
xdrv_23_zigbee_converters . ino - zigbee support for Sonoff - Tasmota
Copyright ( C ) 2019 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
/*********************************************************************************************\
* ZCL
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
typedef union ZCLHeaderFrameControl_t {
struct {
uint8_t frame_type : 2 ; // 00 = across entire profile, 01 = cluster specific
uint8_t manuf_specific : 1 ; // Manufacturer Specific Sub-field
uint8_t direction : 1 ; // 0 = tasmota to zigbee, 1 = zigbee to tasmota
uint8_t disable_def_resp : 1 ; // don't send back default response
uint8_t reserved : 3 ;
} b ;
uint32_t d8 ; // raw 8 bits field
} ZCLHeaderFrameControl_t ;
class ZCLFrame {
public :
ZCLFrame ( uint8_t frame_control , uint16_t manuf_code , uint8_t transact_seq , uint8_t cmd_id ,
const char * buf , size_t buf_len , uint16_t clusterid = 0 , uint16_t groupid = 0 ) :
_cmd_id ( cmd_id ) , _manuf_code ( manuf_code ) , _transact_seq ( transact_seq ) ,
_payload ( buf_len ? buf_len : 250 ) , // allocate the data frame from source or preallocate big enough
_cluster_id ( clusterid ) , _group_id ( groupid )
{
_frame_control . d8 = frame_control ;
_payload . addBuffer ( buf , buf_len ) ;
} ;
void publishMQTTReceived ( uint16_t groupid , uint16_t clusterid , Z_ShortAddress srcaddr ,
uint8_t srcendpoint , uint8_t dstendpoint , uint8_t wasbroadcast ,
uint8_t linkquality , uint8_t securityuse , uint8_t seqnumber ,
uint32_t timestamp ) {
2019-10-13 11:56:52 +01:00
# ifdef ZIGBEE_VERBOSE
2019-09-15 10:10:59 +01:00
char hex_char [ _payload . len ( ) * 2 + 2 ] ;
ToHex_P ( ( unsigned char * ) _payload . getBuffer ( ) , _payload . len ( ) , hex_char , sizeof ( hex_char ) ) ;
2019-10-06 11:40:58 +01:00
Response_P ( PSTR ( " { \" " D_JSON_ZIGBEEZCL_RECEIVED " \" :{ "
2019-09-15 10:10:59 +01:00
" \" groupid \" :%d, " " \" clusterid \" :%d, " " \" srcaddr \" : \" 0x%04X \" , "
" \" srcendpoint \" :%d, " " \" dstendpoint \" :%d, " " \" wasbroadcast \" :%d, "
" \" linkquality \" :%d, " " \" securityuse \" :%d, " " \" seqnumber \" :%d, "
" \" timestamp \" :%d, "
" \" fc \" : \" 0x%02X \" , \" manuf \" : \" 0x%04X \" , \" transact \" :%d, "
" \" cmdid \" : \" 0x%02X \" , \" payload \" : \" %s \" " ) ,
groupid , clusterid , srcaddr ,
srcendpoint , dstendpoint , wasbroadcast ,
linkquality , securityuse , seqnumber ,
timestamp ,
_frame_control , _manuf_code , _transact_seq , _cmd_id ,
hex_char ) ;
ResponseJsonEnd ( ) ; // append '}'
ResponseJsonEnd ( ) ; // append '}'
2019-10-06 11:40:58 +01:00
MqttPublishPrefixTopic_P ( RESULT_OR_TELE , PSTR ( D_JSON_ZIGBEEZCL_RECEIVED ) ) ;
2019-09-15 10:10:59 +01:00
XdrvRulesProcess ( ) ;
2019-10-13 11:56:52 +01:00
# endif
2019-09-15 10:10:59 +01:00
}
2019-09-19 18:25:08 +01:00
static ZCLFrame parseRawFrame ( const SBuffer & buf , uint8_t offset , uint8_t len , uint16_t clusterid , uint16_t groupid ) { // parse a raw frame and build the ZCL frame object
2019-09-15 10:10:59 +01:00
uint32_t i = offset ;
ZCLHeaderFrameControl_t frame_control ;
uint16_t manuf_code = 0 ;
uint8_t transact_seq ;
uint8_t cmd_id ;
frame_control . d8 = buf . get8 ( i + + ) ;
if ( frame_control . b . manuf_specific ) {
manuf_code = buf . get16 ( i ) ;
i + = 2 ;
}
transact_seq = buf . get8 ( i + + ) ;
cmd_id = buf . get8 ( i + + ) ;
ZCLFrame zcl_frame ( frame_control . d8 , manuf_code , transact_seq , cmd_id ,
( const char * ) ( buf . buf ( ) + i ) , len + offset - i ,
clusterid , groupid ) ;
return zcl_frame ;
}
bool isClusterSpecificCommand ( void ) {
return _frame_control . b . frame_type & 1 ;
}
void parseRawAttributes ( JsonObject & json , uint8_t offset = 0 ) ;
2019-10-06 11:40:58 +01:00
void parseReadAttributes ( JsonObject & json , uint8_t offset = 0 ) ;
2019-09-15 10:10:59 +01:00
void parseClusterSpecificCommand ( JsonObject & json , uint8_t offset = 0 ) ;
2019-10-06 11:40:58 +01:00
void postProcessAttributes ( uint16_t shortaddr , JsonObject & json ) ;
2019-09-15 10:10:59 +01:00
inline void setGroupId ( uint16_t groupid ) {
_group_id = groupid ;
}
inline void setClusterId ( uint16_t clusterid ) {
_cluster_id = clusterid ;
}
inline uint8_t getCmdId ( void ) const {
return _cmd_id ;
}
inline uint16_t getClusterId ( void ) const {
return _cluster_id ;
}
const SBuffer & getPayload ( void ) const {
return _payload ;
}
private :
ZCLHeaderFrameControl_t _frame_control = { . d8 = 0 } ;
uint16_t _manuf_code = 0 ; // optional
uint8_t _transact_seq = 0 ; // transaction sequence number
uint8_t _cmd_id = 0 ;
uint16_t _cluster_id = 0 ;
uint16_t _group_id = 0 ;
SBuffer _payload ;
} ;
// Zigbee ZCL converters
// from https://github.com/Koenkk/zigbee-shepherd-converters/blob/638d29f0cace6343052b9a4e7fd60980fa785479/converters/fromZigbee.js#L55
// Input voltage in mV, i.e. 3000 = 3.000V
// Output percentage from 0 to 100 as int
uint8_t toPercentageCR2032 ( uint32_t voltage ) {
uint32_t percentage ;
if ( voltage < 2100 ) {
percentage = 0 ;
} else if ( voltage < 2440 ) {
percentage = 6 - ( ( 2440 - voltage ) * 6 ) / 340 ;
} else if ( voltage < 2740 ) {
percentage = 18 - ( ( 2740 - voltage ) * 12 ) / 300 ;
} else if ( voltage < 2900 ) {
percentage = 42 - ( ( 2900 - voltage ) * 24 ) / 160 ;
} else if ( voltage < 3000 ) {
percentage = 100 - ( ( 3000 - voltage ) * 58 ) / 100 ;
} else if ( voltage > = 3000 ) {
percentage = 100 ;
}
return percentage ;
}
uint32_t parseSingleAttribute ( JsonObject & json , char * attrid_str , class SBuffer & buf ,
uint32_t offset , uint32_t len ) {
uint32_t i = offset ;
uint32_t attrtype = buf . get8 ( i + + ) ;
// fallback - enter a null value
json [ attrid_str ] = ( char * ) nullptr ;
// now parse accordingly to attr type
switch ( attrtype ) {
case 0x00 : // nodata
case 0xFF : // unk
break ;
case 0x10 : // bool
{
uint8_t val_bool = buf . get8 ( i + + ) ;
if ( 0xFF ! = val_bool ) {
json [ attrid_str ] = ( bool ) ( val_bool ? true : false ) ;
}
}
break ;
case 0x20 : // uint8
{
uint8_t uint8_val = buf . get8 ( i ) ;
i + = 1 ;
if ( 0xFF ! = uint8_val ) {
json [ attrid_str ] = uint8_val ;
}
}
break ;
case 0x21 : // uint16
{
uint16_t uint16_val = buf . get16 ( i ) ;
i + = 2 ;
if ( 0xFFFF ! = uint16_val ) {
json [ attrid_str ] = uint16_val ;
}
}
break ;
2019-10-06 11:40:58 +01:00
case 0x23 : // uint32
2019-09-15 10:10:59 +01:00
{
uint32_t uint32_val = buf . get32 ( i ) ;
i + = 4 ;
if ( 0xFFFFFFFF ! = uint32_val ) {
json [ attrid_str ] = uint32_val ;
}
}
break ;
// Note: uint40, uint48, uint56, uint64 are not used in ZCL, so they are not implemented (yet)
case 0x24 : // int40
case 0x25 : // int48
case 0x26 : // int56
case 0x27 : // int64
i + = attrtype - 0x1F ; // 5 - 8;
break ;
case 0x28 : // uint8
{
int8_t int8_val = buf . get8 ( i ) ;
i + = 1 ;
if ( 0x80 ! = int8_val ) {
json [ attrid_str ] = int8_val ;
}
}
break ;
case 0x29 : // uint16
{
int16_t int16_val = buf . get16 ( i ) ;
i + = 2 ;
if ( 0x8000 ! = int16_val ) {
json [ attrid_str ] = int16_val ;
}
}
break ;
case 0x2B : // uint16
{
int32_t int32_val = buf . get32 ( i ) ;
i + = 4 ;
if ( 0x80000000 ! = int32_val ) {
json [ attrid_str ] = int32_val ;
}
}
break ;
// Note: int40, int48, int56, int64 are not used in ZCL, so they are not implemented (yet)
case 0x2C : // int40
case 0x2D : // int48
case 0x2E : // int56
case 0x2F : // int64
i + = attrtype - 0x27 ; // 5 - 8;
break ;
case 0x41 : // octet string, 1 byte len
case 0x42 : // char string, 1 byte len
case 0x43 : // octet string, 2 bytes len
case 0x44 : // char string, 2 bytes len
// For strings, default is to try to do a real string, but reverts to octet stream if null char is present or on some exceptions
{
bool parse_as_string = true ;
uint32_t len = ( attrtype < = 0x42 ) ? buf . get8 ( i ) : buf . get16 ( i ) ; // len is 8 or 16 bits
i + = ( attrtype < = 0x42 ) ? 1 : 2 ; // increment pointer
// check if we can safely use a string
if ( ( 0x41 = = attrtype ) | | ( 0x43 = = attrtype ) ) { parse_as_string = false ; }
else {
for ( uint32_t j = 0 ; j < len ; j + + ) {
if ( 0x00 = = buf . get8 ( i + j ) ) {
parse_as_string = false ;
break ;
}
}
}
if ( parse_as_string ) {
char str [ len + 1 ] ;
strncpy ( str , buf . charptr ( i ) , len ) ;
str [ len ] = 0x00 ;
json [ attrid_str ] = str ;
} else {
// print as HEX
char hex [ 2 * len + 1 ] ;
ToHex_P ( buf . buf ( i ) , len , hex , sizeof ( hex ) ) ;
json [ attrid_str ] = hex ;
}
i + = len ;
break ;
}
i + = buf . get8 ( i ) + 1 ;
break ;
case 0x08 : // data8
case 0x18 : // map8
2019-10-13 11:56:52 +01:00
{
uint8_t uint8_val = buf . get8 ( i ) ;
i + = 1 ;
json [ attrid_str ] = uint8_val ;
}
2019-09-15 10:10:59 +01:00
break ;
2019-10-13 11:56:52 +01:00
case 0x09 : // data16
2019-09-15 10:10:59 +01:00
case 0x19 : // map16
2019-10-13 11:56:52 +01:00
{
uint16_t uint16_val = buf . get16 ( i ) ;
i + = 2 ;
json [ attrid_str ] = uint16_val ;
}
2019-09-15 10:10:59 +01:00
break ;
2019-10-13 11:56:52 +01:00
case 0x0B : // data32
2019-09-15 10:10:59 +01:00
case 0x1B : // map32
2019-10-13 11:56:52 +01:00
{
uint32_t uint32_val = buf . get32 ( i ) ;
i + = 4 ;
json [ attrid_str ] = uint32_val ;
}
2019-09-15 10:10:59 +01:00
break ;
// enum
case 0x30 : // enum8
case 0x31 : // enum16
i + = attrtype - 0x2F ;
break ;
2019-10-13 11:56:52 +01:00
// TODO
2019-09-15 10:10:59 +01:00
case 0x39 : // float
i + = 4 ;
break ;
case 0xE0 : // ToD
case 0xE1 : // date
case 0xE2 : // UTC
i + = 4 ;
break ;
case 0xE8 : // clusterId
case 0xE9 : // attribId
i + = 2 ;
break ;
case 0xEA : // bacOID
i + = 4 ;
break ;
case 0xF0 : // EUI64
i + = 8 ;
break ;
case 0xF1 : // key128
i + = 16 ;
break ;
// Other un-implemented data types
case 0x0A : // data24
case 0x0C : // data40
case 0x0D : // data48
case 0x0E : // data56
case 0x0F : // data64
i + = attrtype - 0x07 ; // 2-8
break ;
// map<x>
case 0x1A : // map24
case 0x1C : // map40
case 0x1D : // map48
case 0x1E : // map56
case 0x1F : // map64
i + = attrtype - 0x17 ;
break ;
// semi
case 0x38 : // semi (float on 2 bytes)
i + = 2 ;
break ;
case 0x3A : // double precision
i + = 8 ;
break ;
}
// String pp; // pretty print
// json[attrid_str].prettyPrintTo(pp);
// // now store the attribute
// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: ZCL attribute decoded, id %s, type 0x%02X, val=%s"),
// attrid_str, attrtype, pp.c_str());
return i - offset ; // how much have we increased the index
}
// First pass, parse all attributes in their native format
void ZCLFrame : : parseRawAttributes ( JsonObject & json , uint8_t offset ) {
uint32_t i = offset ;
uint32_t len = _payload . len ( ) ;
2019-09-29 14:38:26 +01:00
while ( len - i > = 3 ) {
uint16_t attrid = _payload . get16 ( i ) ;
2019-09-15 10:10:59 +01:00
i + = 2 ;
2019-10-13 11:56:52 +01:00
char key [ 16 ] ;
snprintf_P ( key , sizeof ( key ) , PSTR ( " %04X_%02X_%04X " ) ,
_cluster_id , _cmd_id , attrid ) ;
2019-09-15 10:10:59 +01:00
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
2019-09-29 14:38:26 +01:00
if ( ( 0x0000 = = _cluster_id ) & & ( 0xFF01 = = attrid ) ) {
2019-09-15 10:10:59 +01:00
if ( 0x42 = = _payload . get8 ( i ) ) {
_payload . set8 ( i , 0x41 ) ; // change type from 0x42 to 0x41
}
}
2019-10-13 11:56:52 +01:00
i + = parseSingleAttribute ( json , key , _payload , i , len ) ;
2019-09-15 10:10:59 +01:00
}
}
2019-10-06 11:40:58 +01:00
// ZCL_READ_ATTRIBUTES_RESPONSE
void ZCLFrame : : parseReadAttributes ( JsonObject & json , uint8_t offset ) {
uint32_t i = offset ;
uint32_t len = _payload . len ( ) ;
while ( len - i > = 4 ) {
uint16_t attrid = _payload . get16 ( i ) ;
i + = 2 ;
uint8_t status = _payload . get8 ( i + + ) ;
if ( 0 = = status ) {
2019-10-13 11:56:52 +01:00
char key [ 16 ] ;
snprintf_P ( key , sizeof ( key ) , PSTR ( " %04X_%02X_%04X " ) ,
_cluster_id , _cmd_id , attrid ) ;
2019-10-06 11:40:58 +01:00
2019-10-13 11:56:52 +01:00
i + = parseSingleAttribute ( json , key , _payload , i , len ) ;
2019-10-06 11:40:58 +01:00
}
}
}
2019-09-15 10:10:59 +01:00
// Parse non-normalized attributes
2019-09-29 14:38:26 +01:00
// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id
2019-09-15 10:10:59 +01:00
void ZCLFrame : : parseClusterSpecificCommand ( JsonObject & json , uint8_t offset ) {
uint32_t i = offset ;
uint32_t len = _payload . len ( ) ;
char attrid_str [ 12 ] ;
2019-10-13 11:56:52 +01:00
snprintf_P ( attrid_str , sizeof ( attrid_str ) , PSTR ( " %04X!%02X " ) , _cmd_id , _cluster_id ) ;
2019-09-15 10:10:59 +01:00
char hex_char [ _payload . len ( ) * 2 + 2 ] ;
ToHex_P ( ( unsigned char * ) _payload . getBuffer ( ) , _payload . len ( ) , hex_char , sizeof ( hex_char ) ) ;
json [ attrid_str ] = hex_char ;
}
2019-09-29 14:38:26 +01:00
// return value:
// 0 = keep initial value
// 1 = remove initial value
2019-10-06 11:40:58 +01:00
typedef int32_t ( * Z_AttrConverter ) ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) ;
2019-09-29 14:38:26 +01:00
typedef struct Z_AttributeConverter {
const char * filter ;
const char * name ;
Z_AttrConverter func ;
void * param ;
} Z_AttributeConverter ;
const float Z_100 PROGMEM = 100.0f ;
const float Z_10 PROGMEM = 10.0f ;
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess [ ] = {
2019-10-13 11:56:52 +01:00
{ " 0000_0?_0004 " , nullptr , & Z_ManufKeep , nullptr } , // record Manufacturer
{ " 0000_0?_0005 " , nullptr , & Z_ModelKeep , nullptr } , // record Model
2019-10-06 11:40:58 +01:00
2019-10-13 11:56:52 +01:00
{ " 0000_0?_0000 " , " ZCLVersion " , & Z_Copy , nullptr } ,
{ " 0000_0?_0001 " , " AppVersion " , & Z_Copy , nullptr } ,
{ " 0000_0?_0002 " , " StackVersion " , & Z_Copy , nullptr } ,
{ " 0000_0?_0003 " , " HWVersion " , & Z_Copy , nullptr } ,
{ " 0000_0?_0004 " , " Manufacturer " , & Z_Copy , nullptr } ,
{ " 0000_0?_0005 " , D_JSON_MODEL D_JSON_ID , & Z_Copy , nullptr } ,
{ " 0000_0?_0006 " , " DateCode " , & Z_Copy , nullptr } ,
{ " 0000_0?_0007 " , " PowerSource " , & Z_Copy , nullptr } ,
{ " 0000_0?_4000 " , " SWBuildID " , & Z_Copy , nullptr } ,
{ " 0000_0?_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0400_0A_0000 " , D_JSON_ILLUMINANCE , & Z_Copy , nullptr } , // Illuminance (in Lux)
{ " 0400_0A_0004 " , " LightSensorType " , & Z_Copy , nullptr } , // LightSensorType
{ " 0400_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0401_0A_0000 " , " LevelStatus " , & Z_Copy , nullptr } , // Illuminance (in Lux)
{ " 0401_0A_0001 " , " LightSensorType " , & Z_Copy , nullptr } , // LightSensorType
{ " 0401_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0402_0A_0000 " , D_JSON_TEMPERATURE , & Z_ConvFloatDivider , ( void * ) & Z_100 } , // Temperature
{ " 0402_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0403_0A_0000 " , D_JSON_PRESSURE_UNIT , & Z_Const_Keep , ( void * ) D_UNIT_PRESSURE } , // Pressure Unit
{ " 0403_0A_0000 " , D_JSON_PRESSURE , & Z_Copy , nullptr } , // Pressure
{ " 0403_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other Pressure values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0404_0A_0000 " , D_JSON_FLOWRATE , & Z_ConvFloatDivider , ( void * ) & Z_10 } , // Flow (in m3/h)
{ " 0404_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0405_0A_0000 " , D_JSON_HUMIDITY , & Z_ConvFloatDivider , ( void * ) & Z_100 } , // Humidity
{ " 0405_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
2019-10-13 11:56:52 +01:00
{ " 0406_0A_0000 " , " Occupancy " , & Z_Copy , nullptr } , // Occupancy (map8)
{ " 0406_0A_0001 " , " OccupancySensorType " , & Z_Copy , nullptr } , // OccupancySensorType
{ " 0406_0A_???? " , nullptr , & Z_Remove , nullptr } , // Remove all other values
2019-09-29 14:38:26 +01:00
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
2019-10-13 11:56:52 +01:00
{ " 0000_0A_FF01 " , nullptr , & Z_AqaraSensor , nullptr } , // Occupancy (map8)
2019-09-29 14:38:26 +01:00
// // 0x0b04 Electrical Measurement
2019-10-13 11:56:52 +01:00
// { "0B04_0100", "DCVoltage", &Z_Copy, nullptr }, //
// { "0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, //
// { "0B04_????", "", &Z_Remove, nullptr }, //
2019-09-29 14:38:26 +01:00
} ;
2019-09-15 10:10:59 +01:00
2019-10-06 11:40:58 +01:00
// ======================================================================
// Record Manuf
int32_t Z_ManufKeep ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
zigbee_devices . setManufId ( shortaddr , value . as < const char * > ( ) ) ;
return 0 ; // keep original key
}
//
int32_t Z_ModelKeep ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
zigbee_devices . setModelId ( shortaddr , value . as < const char * > ( ) ) ;
return 0 ; // keep original key
}
2019-09-29 14:38:26 +01:00
// ======================================================================
// Remove attribute
2019-10-06 11:40:58 +01:00
int32_t Z_Remove ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
2019-09-29 14:38:26 +01:00
return 1 ; // remove original key
}
2019-09-15 10:10:59 +01:00
2019-09-29 14:38:26 +01:00
// Copy value as-is
2019-10-06 11:40:58 +01:00
int32_t Z_Copy ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
2019-09-29 14:38:26 +01:00
json [ new_name ] = value ;
return 1 ; // remove original key
}
2019-09-15 10:10:59 +01:00
2019-09-29 14:38:26 +01:00
// Copy value as-is
2019-10-06 11:40:58 +01:00
int32_t Z_Const_Keep ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
2019-09-29 14:38:26 +01:00
json [ new_name ] = ( char * ) param ;
return 0 ; // keep original key
}
2019-09-15 10:10:59 +01:00
2019-09-29 14:38:26 +01:00
// Convert int to float with divider
2019-10-06 11:40:58 +01:00
int32_t Z_ConvFloatDivider ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
2019-09-29 14:38:26 +01:00
float f_value = value ;
float * divider = ( float * ) param ;
json [ new_name ] = f_value / * divider ;
return 1 ; // remove original key
}
2019-09-15 10:10:59 +01:00
2019-10-06 11:40:58 +01:00
int32_t Z_AqaraSensor ( uint16_t shortaddr , JsonObject & json , const char * name , JsonVariant & value , const char * new_name , void * param ) {
2019-09-29 14:38:26 +01:00
String hex = value ;
SBuffer buf2 = SBuffer : : SBufferFromHex ( hex . c_str ( ) , hex . length ( ) ) ;
uint32_t i = 0 ;
uint32_t len = buf2 . len ( ) ;
char tmp [ ] = " tmp " ; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
JsonVariant sub_value ;
while ( len - i > = 2 ) {
uint8_t attrid = buf2 . get8 ( i + + ) ;
i + = parseSingleAttribute ( json , tmp , buf2 , i , len ) ;
float val = json [ tmp ] ;
json . remove ( tmp ) ;
if ( 0x64 = = attrid ) {
json [ F ( D_JSON_TEMPERATURE ) ] = val / 100.0f ;
} else if ( 0x65 = = attrid ) {
json [ F ( D_JSON_HUMIDITY ) ] = val / 100.0f ;
} else if ( 0x66 = = attrid ) {
json [ F ( D_JSON_PRESSURE ) ] = val / 100.0f ;
json [ F ( D_JSON_PRESSURE_UNIT ) ] = F ( D_UNIT_PRESSURE ) ; // hPa
} else if ( 0x01 = = attrid ) {
json [ F ( D_JSON_VOLTAGE ) ] = val / 1000.0f ;
json [ F ( " Battery " ) ] = toPercentageCR2032 ( val ) ;
2019-09-15 10:10:59 +01:00
}
}
2019-09-29 14:38:26 +01:00
return 1 ; // remove original key
}
// ======================================================================
2019-10-13 11:56:52 +01:00
// #define ZCL_MODELID "A_0000_0005" // Cmd 0x0A - Cluster 0x0000, attribute 0x05
// #define ZCL_TEMPERATURE "A_0402_0000" // Cmd 0x0A - Cluster 0x0402, attribute 0x00
// #define ZCL_PRESSURE "A_0403_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
// #define ZCL_PRESSURE_SCALED "A_0403_0010" // Cmd 0x0A - Cluster 0x0403, attribute 0x10
// #define ZCL_PRESSURE_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
// #define ZCL_HUMIDITY "A_0405_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
// #define ZCL_LUMI_WEATHER "A_0000_FF01" // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
2019-09-29 14:38:26 +01:00
// Cluster Specific commands
# define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
# define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
# define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
# define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
# define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
# define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
# define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
# define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
# define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
# define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
# define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
// inspired from https://github.com/torvalds/linux/blob/master/lib/glob.c
bool mini_glob_match ( char const * pat , char const * str ) {
for ( ; ; ) {
unsigned char c = * str + + ;
unsigned char d = * pat + + ;
switch ( d ) {
case ' ? ' : /* Wildcard: anything but nul */
if ( c = = ' \0 ' )
return false ;
break ;
case ' \\ ' :
d = * pat + + ;
/*FALLTHROUGH*/
default : /* Literal character */
if ( c = = d ) {
if ( d = = ' \0 ' )
return true ;
break ;
}
return false ; /* No point continuing */
}
}
}
2019-09-15 10:10:59 +01:00
2019-10-06 11:40:58 +01:00
void ZCLFrame : : postProcessAttributes ( uint16_t shortaddr , JsonObject & json ) {
2019-09-29 14:38:26 +01:00
// iterate on json elements
for ( auto kv : json ) {
String key = kv . key ;
JsonVariant & value = kv . value ;
// Iterate on filter
for ( uint32_t i = 0 ; i < sizeof ( Z_PostProcess ) / sizeof ( Z_PostProcess [ 0 ] ) ; i + + ) {
const Z_AttributeConverter * converter = & Z_PostProcess [ i ] ;
if ( mini_glob_match ( converter - > filter , key . c_str ( ) ) ) {
2019-10-06 11:40:58 +01:00
int32_t drop = ( * converter - > func ) ( shortaddr , json , key . c_str ( ) , value , converter - > name , converter - > param ) ;
2019-09-29 14:38:26 +01:00
if ( drop ) {
json . remove ( key ) ;
}
2019-09-15 10:10:59 +01:00
2019-09-29 14:38:26 +01:00
}
2019-09-15 10:10:59 +01:00
}
}
}
2019-09-29 14:38:26 +01:00
//void ZCLFrame::postProcessAttributes2(JsonObject& json) {
// void postProcessAttributes2(JsonObject& json) {
// const __FlashStringHelper *key;
//
// // Osram Mini Switch
// key = F(ZCL_OO_OFF);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("Off");
// }
// key = F(ZCL_OO_ON);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("On");
// }
// key = F(ZCL_COLORTEMP_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint16_t color_temp = buf2.get16(0);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("ColorTemp")] = color_temp;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == level) {
// json[F(D_CMND_POWER)] = F("Off");
// } else {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_1);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// }
// key = F(ZCL_LC_MOVE_1_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// if (0 == move_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STEP);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_STEP_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == step_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STOP);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
// key = F(ZCL_LC_STOP_WOO);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
//
// // Lumi.weather proprietary field
// key = F(ZCL_LUMI_WEATHER);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// DynamicJsonBuffer jsonBuffer;
// JsonObject& json_lumi = jsonBuffer.createObject();
// uint32_t i = 0;
// uint32_t len = buf2.len();
// char shortaddr[8];
//
// while (len - i >= 2) {
// uint8_t attrid = buf2.get8(i++);
//
// snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
//
// //json[shortaddr] = parseSingleAttribute(json_lumi, buf2, i, len, nullptr, 0);
// }
// // parse output
// if (json_lumi.containsKey("0x64")) { // Temperature
// int32_t temperature = json_lumi["0x64"];
// json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
// }
// if (json_lumi.containsKey("0x65")) { // Humidity
// uint32_t humidity = json_lumi["0x65"];
// json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
// }
// if (json_lumi.containsKey("0x66")) { // Pressure
// int32_t pressure = json_lumi["0x66"];
// json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
// json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
// }
// if (json_lumi.containsKey("0x01")) { // Battery Voltage
// uint32_t voltage = json_lumi["0x01"];
// json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
// json[F("Battery")] = toPercentageCR2032(voltage);
// }
// json.remove(key);
// }
//
// }
2019-09-15 10:10:59 +01:00
# endif // USE_ZIGBEE