mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-20 09:16:30 +00:00
Zigbee command `ZbInfo
` and prepare support for EEPROM
This commit is contained in:
parent
0fd6a65a56
commit
39b0cf4c56
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Zigbee alarm persistence (#9785)
|
- Zigbee alarm persistence (#9785)
|
||||||
- Support for EZO PMP sensors by Christopher Tremblay (#9760)
|
- Support for EZO PMP sensors by Christopher Tremblay (#9760)
|
||||||
- Commands ``TuyaRGB``, ``TuyaEnum`` and ``TuyaEnumList`` (#9769)
|
- Commands ``TuyaRGB``, ``TuyaEnum`` and ``TuyaEnumList`` (#9769)
|
||||||
|
- Zigbee command ``ZbInfo`` and prepare support for EEPROM
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Core library from v2.7.4.5 to v2.7.4.7
|
- Core library from v2.7.4.5 to v2.7.4.7
|
||||||
|
@ -227,7 +227,9 @@ Eeprom24C512::readBytes
|
|||||||
|
|
||||||
byte remainingBytes = length % EEPROM__RD_BUFFER_SIZE;
|
byte remainingBytes = length % EEPROM__RD_BUFFER_SIZE;
|
||||||
word offset = length - remainingBytes;
|
word offset = length - remainingBytes;
|
||||||
readBuffer(address + offset, remainingBytes, p_data + offset);
|
if (remainingBytes > 0) {
|
||||||
|
readBuffer(address + offset, remainingBytes, p_data + offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
@ -547,6 +547,7 @@
|
|||||||
#define D_JSON_ZIGBEE_MODELID "ModelId"
|
#define D_JSON_ZIGBEE_MODELID "ModelId"
|
||||||
#define D_CMND_ZIGBEE_PROBE "Probe"
|
#define D_CMND_ZIGBEE_PROBE "Probe"
|
||||||
#define D_CMND_ZIGBEE_FORGET "Forget"
|
#define D_CMND_ZIGBEE_FORGET "Forget"
|
||||||
|
#define D_CMND_ZIGBEE_INFO "Info"
|
||||||
#define D_CMND_ZIGBEE_SAVE "Save"
|
#define D_CMND_ZIGBEE_SAVE "Save"
|
||||||
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
||||||
#define D_CMND_ZIGBEE_CLUSTER "Cluster"
|
#define D_CMND_ZIGBEE_CLUSTER "Cluster"
|
||||||
|
@ -730,6 +730,7 @@
|
|||||||
#define USE_ZIGBEE_MODELID "Tasmota Z2T" // reported "ModelId" (cluster 0000 / attribute 0005)
|
#define USE_ZIGBEE_MODELID "Tasmota Z2T" // reported "ModelId" (cluster 0000 / attribute 0005)
|
||||||
#define USE_ZIGBEE_MANUFACTURER "Tasmota" // reported "Manufacturer" (cluster 0000 / attribute 0004)
|
#define USE_ZIGBEE_MANUFACTURER "Tasmota" // reported "Manufacturer" (cluster 0000 / attribute 0004)
|
||||||
#define USE_ZBBRIDGE_TLS // TLS support for zbbridge
|
#define USE_ZBBRIDGE_TLS // TLS support for zbbridge
|
||||||
|
#define USE_ZIGBEE_ZBBRIDGE_EEPROM 0x50 // I2C id for the ZBBridge EEPROM
|
||||||
|
|
||||||
// -- Other sensors/drivers -----------------------
|
// -- Other sensors/drivers -----------------------
|
||||||
|
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
|
|
||||||
#ifdef USE_ZIGBEE
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
|
#ifdef USE_ZIGBEE_EZSP
|
||||||
|
#include "Eeprom24C512.h"
|
||||||
|
#endif // USE_ZIGBEE_EZSP
|
||||||
|
|
||||||
// contains some definitions for functions used before their declarations
|
// contains some definitions for functions used before their declarations
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -69,7 +73,14 @@ const uint8_t ZIGBEE_LABEL_CONFIGURE_EZSP = 53; // main loop
|
|||||||
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
|
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
|
||||||
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||||
|
|
||||||
struct ZigbeeStatus {
|
class ZigbeeStatus {
|
||||||
|
public:
|
||||||
|
ZigbeeStatus()
|
||||||
|
#ifdef USE_ZIGBEE_EZSP
|
||||||
|
: eeprom(USE_ZIGBEE_ZBBRIDGE_EEPROM)
|
||||||
|
#endif // USE_ZIGBEE_EZSP
|
||||||
|
{}
|
||||||
|
|
||||||
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
|
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
|
||||||
bool state_machine = false; // the state machine is running
|
bool state_machine = false; // the state machine is running
|
||||||
bool state_waiting = false; // the state machine is waiting for external event or timeout
|
bool state_waiting = false; // the state machine is waiting for external event or timeout
|
||||||
@ -77,6 +88,7 @@ struct ZigbeeStatus {
|
|||||||
bool ready = false; // cc2530 initialization is complet, ready to operate
|
bool ready = false; // cc2530 initialization is complet, ready to operate
|
||||||
bool init_phase = true; // initialization phase, before accepting zigbee traffic
|
bool init_phase = true; // initialization phase, before accepting zigbee traffic
|
||||||
bool recv_until = false; // ignore all messages until the received frame fully matches
|
bool recv_until = false; // ignore all messages until the received frame fully matches
|
||||||
|
bool eeprom_present = false; // is the ZBBridge EEPROM present?
|
||||||
|
|
||||||
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
||||||
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
||||||
@ -89,6 +101,10 @@ struct ZigbeeStatus {
|
|||||||
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
|
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
|
||||||
|
|
||||||
uint32_t permit_end_time = 0; // timestamp when permit join ends
|
uint32_t permit_end_time = 0; // timestamp when permit join ends
|
||||||
|
|
||||||
|
#ifdef USE_ZIGBEE_EZSP
|
||||||
|
Eeprom24C512 eeprom; // takes only 1 bytes of RAM
|
||||||
|
#endif // USE_ZIGBEE_EZSP
|
||||||
};
|
};
|
||||||
struct ZigbeeStatus zigbee;
|
struct ZigbeeStatus zigbee;
|
||||||
SBuffer *zigbee_buffer = nullptr;
|
SBuffer *zigbee_buffer = nullptr;
|
||||||
|
@ -53,7 +53,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = {
|
|||||||
'\0', // 0x0C
|
'\0', // 0x0C
|
||||||
'\0', // 0x0D
|
'\0', // 0x0D
|
||||||
'\0', // 0x0E
|
'\0', // 0x0E
|
||||||
'E', // 0x05 Z_Data_Type::Z_Ext
|
'E', // 0x0F Z_Data_Type::Z_Ext
|
||||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,12 +63,13 @@ class Z_Data_Set;
|
|||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
class Z_Data {
|
class Z_Data {
|
||||||
public:
|
public:
|
||||||
Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _power(0) {}
|
Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _align_1(0), _reserved(0) {}
|
||||||
inline Z_Data_Type getType(void) const { return _type; }
|
inline Z_Data_Type getType(void) const { return _type; }
|
||||||
inline int8_t getConfig(void) const { return _config; }
|
inline int8_t getConfig(void) const { return _config; }
|
||||||
inline bool validConfig(void) const { return _config != 0xF; }
|
inline bool validConfig(void) const { return _config != 0xF; }
|
||||||
inline void setConfig(int8_t config) { _config = config; }
|
inline void setConfig(int8_t config) { _config = config; }
|
||||||
uint8_t getConfigByte(void) const { return ( ((uint8_t)_type) << 4) | ((_config & 0xF) & 0x0F); }
|
uint8_t getConfigByte(void) const { return ( ((uint8_t)_type) << 4) | ((_config & 0xF) & 0x0F); }
|
||||||
|
uint8_t getReserved(void) const { return _reserved; }
|
||||||
|
|
||||||
inline uint8_t getEndpoint(void) const { return _endpoint; }
|
inline uint8_t getEndpoint(void) const { return _endpoint; }
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ public:
|
|||||||
inline bool update(void) { return false; }
|
inline bool update(void) { return false; }
|
||||||
|
|
||||||
static const Z_Data_Type type = Z_Data_Type::Z_Unknown;
|
static const Z_Data_Type type = Z_Data_Type::Z_Unknown;
|
||||||
|
static size_t DataTypeToLength(Z_Data_Type t);
|
||||||
static bool ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config);
|
static bool ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config);
|
||||||
|
|
||||||
static Z_Data_Type CharToDataType(char c);
|
static Z_Data_Type CharToDataType(char c);
|
||||||
@ -87,9 +89,10 @@ public:
|
|||||||
friend class Z_Data_Set;
|
friend class Z_Data_Set;
|
||||||
protected:
|
protected:
|
||||||
Z_Data_Type _type; // encoded on 4 bits, type of the device
|
Z_Data_Type _type; // encoded on 4 bits, type of the device
|
||||||
uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint
|
uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint
|
||||||
uint8_t _config : 4; // encoded on 4 bits, customize behavior
|
uint8_t _config : 4; // encoded on 4 bits, customize behavior
|
||||||
uint8_t _power; // power state if the type supports it
|
uint8_t _align_1 : 4; // force aligned to bytes, and fill with zero
|
||||||
|
uint8_t _reserved; // power state if the type supports it
|
||||||
};
|
};
|
||||||
|
|
||||||
Z_Data_Type Z_Data::CharToDataType(char c) {
|
Z_Data_Type Z_Data::CharToDataType(char c) {
|
||||||
@ -153,28 +156,20 @@ bool Z_Data::ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t
|
|||||||
class Z_Data_OnOff : public Z_Data {
|
class Z_Data_OnOff : public Z_Data {
|
||||||
public:
|
public:
|
||||||
Z_Data_OnOff(uint8_t endpoint = 0) :
|
Z_Data_OnOff(uint8_t endpoint = 0) :
|
||||||
Z_Data(Z_Data_Type::Z_OnOff, endpoint)
|
Z_Data(Z_Data_Type::Z_OnOff, endpoint),
|
||||||
{
|
power(0xFF)
|
||||||
_config = 1; // at least 1 OnOff
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
inline bool validPower(uint32_t relay = 0) const { return (_config > relay); } // power is declared
|
inline bool validPower(void) const { return 0xFF != power; } // power is declared
|
||||||
inline bool getPower(uint32_t relay = 0) const { return bitRead(_power, relay); }
|
inline bool getPower(void) const { return validPower() ? (power != 0) : false; } // default to false if undefined
|
||||||
void setPower(bool val, uint32_t relay = 0);
|
inline void setPower(bool val) { power = val ? 1 : 0; }
|
||||||
|
|
||||||
void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const;
|
|
||||||
|
|
||||||
static const Z_Data_Type type = Z_Data_Type::Z_OnOff;
|
static const Z_Data_Type type = Z_Data_Type::Z_OnOff;
|
||||||
|
|
||||||
|
// 1 byte
|
||||||
|
uint8_t power;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void Z_Data_OnOff::setPower(bool val, uint32_t relay) {
|
|
||||||
if (relay < 8) {
|
|
||||||
if (_config < relay) { _config = relay; } // we update the number of valid relays
|
|
||||||
bitWrite(_power, relay, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Device specific: Plug device
|
* Device specific: Plug device
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
@ -430,6 +425,37 @@ public:
|
|||||||
// 0x0226 Glass break sensor
|
// 0x0226 Glass break sensor
|
||||||
// 0x0229 Security repeater*
|
// 0x0229 Security repeater*
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uint8_t Z_Data_Type_len[] PROGMEM = {
|
||||||
|
0, // 0x00 Z_Data_Type::Z_Unknown
|
||||||
|
sizeof(Z_Data_Light), // 0x01 Z_Data_Type::Z_Light
|
||||||
|
sizeof(Z_Data_Plug), // 0x02 Z_Data_Type::Z_Plug
|
||||||
|
sizeof(Z_Data_PIR), // 0x03 Z_Data_Type::Z_PIR
|
||||||
|
sizeof(Z_Data_Alarm), // 0x04 Z_Data_Type::Z_Alarm
|
||||||
|
sizeof(Z_Data_Thermo), // 0x05 Z_Data_Type::Z_Thermo
|
||||||
|
sizeof(Z_Data_OnOff), // 0x05 Z_Data_Type::Z_OnOff
|
||||||
|
0, // 0x06
|
||||||
|
0, // 0x07
|
||||||
|
0, // 0x08
|
||||||
|
0, // 0x09
|
||||||
|
0, // 0x0A
|
||||||
|
0, // 0x0B
|
||||||
|
0, // 0x0C
|
||||||
|
0, // 0x0D
|
||||||
|
0, // 0x0E
|
||||||
|
0, // 0x0F Z_Data_Type::Z_Ext
|
||||||
|
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t Z_Data::DataTypeToLength(Z_Data_Type t) {
|
||||||
|
uint32_t tt = (uint32_t) t;
|
||||||
|
if (tt < ARRAY_SIZE(Z_Data_Type_len)) {
|
||||||
|
return pgm_read_byte(&Z_Data_Type_len[tt]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
*
|
*
|
||||||
* Device specific Linked List
|
* Device specific Linked List
|
||||||
@ -453,7 +479,10 @@ public:
|
|||||||
template <class M>
|
template <class M>
|
||||||
const M & find(uint8_t ep = 0) const;
|
const M & find(uint8_t ep = 0) const;
|
||||||
|
|
||||||
// check if the point is null, if so create a new object with the right sub-class
|
// create a new data object from a 4 bytes buffer
|
||||||
|
Z_Data & createFromBuffer(const SBuffer & buf, uint32_t start, uint32_t len);
|
||||||
|
|
||||||
|
// check if the pointer is null, if so create a new object with the right sub-class
|
||||||
template <class M>
|
template <class M>
|
||||||
M & addIfNull(M & cur, uint8_t ep = 0);
|
M & addIfNull(M & cur, uint8_t ep = 0);
|
||||||
};
|
};
|
||||||
@ -489,6 +518,35 @@ Z_Data & Z_Data_Set::getByType(Z_Data_Type type, uint8_t ep) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Instanciate with either:
|
||||||
|
// (04)04010100 - without data except minimal Z_Data
|
||||||
|
// (08)04010100.B06DFFFF - with complete data - in this case must not exceed the structure len
|
||||||
|
//
|
||||||
|
// Byte 0: type
|
||||||
|
// Byte 1: endpoint
|
||||||
|
// Byte 2: config
|
||||||
|
// Byte 3: Power
|
||||||
|
Z_Data & Z_Data_Set::createFromBuffer(const SBuffer & buf, uint32_t start, uint32_t len) {
|
||||||
|
if (len < sizeof(Z_Data)) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Invalid len (<4) %d"), len);
|
||||||
|
return *(Z_Data*)nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Z_Data_Type data_type = (Z_Data_Type) buf.get8(start);
|
||||||
|
uint8_t expected_len = Z_Data::DataTypeToLength(data_type);
|
||||||
|
uint8_t endpoint = buf.get8(start + 1);
|
||||||
|
|
||||||
|
Z_Data & elt = getByType(data_type, endpoint);
|
||||||
|
if (&elt == nullptr) { return *(Z_Data*)nullptr; }
|
||||||
|
if (len <= expected_len) {
|
||||||
|
memcpy(&elt, buf.buf(start), len);
|
||||||
|
} else {
|
||||||
|
memcpy(&elt, buf.buf(start), sizeof(Z_Data));
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "buffer len overflow %d > %d"), len, expected_len);
|
||||||
|
}
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
template <class M>
|
template <class M>
|
||||||
M & Z_Data_Set::get(uint8_t ep) {
|
M & Z_Data_Set::get(uint8_t ep) {
|
||||||
M & m = (M&) find(M::type, ep);
|
M & m = (M&) find(M::type, ep);
|
||||||
@ -528,11 +586,6 @@ const Z_Data & Z_Data_Set::find(Z_Data_Type type, uint8_t ep) const {
|
|||||||
return *(Z_Data*)nullptr;
|
return *(Z_Data*)nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Z_Data_OnOff::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const {
|
|
||||||
if (validPower()) { attr_list.addAttribute(PSTR("Power")).setUInt(getPower() ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Structures for Rules variables related to the last received message
|
* Structures for Rules variables related to the last received message
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
@ -561,11 +614,13 @@ public:
|
|||||||
|
|
||||||
// New version of device data handling
|
// New version of device data handling
|
||||||
Z_Data_Set data; // Linkedlist of device data per endpoint
|
Z_Data_Set data; // Linkedlist of device data per endpoint
|
||||||
// other status
|
// other status - device wide data is 8 bytes
|
||||||
|
// START OF DEVICE WIDE DATA
|
||||||
|
uint32_t last_seen; // Last seen time (epoch)
|
||||||
uint8_t lqi; // lqi from last message, 0xFF means unknown
|
uint8_t lqi; // lqi from last message, 0xFF means unknown
|
||||||
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon
|
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon
|
||||||
// power plug data-
|
uint16_t reserved_for_alignment;
|
||||||
uint32_t last_seen; // Last seen time (epoch)
|
// END OF DEVICE WIDE DATA
|
||||||
|
|
||||||
// Constructor with all defaults
|
// Constructor with all defaults
|
||||||
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
||||||
@ -736,7 +791,7 @@ public:
|
|||||||
// Dump json
|
// Dump json
|
||||||
static String dumpLightState(const Z_Device & device);
|
static String dumpLightState(const Z_Device & device);
|
||||||
String dumpDevice(uint32_t dump_mode, const Z_Device & device) const;
|
String dumpDevice(uint32_t dump_mode, const Z_Device & device) const;
|
||||||
static String dumpSingleDevice(uint32_t dump_mode, const Z_Device & device);
|
static String dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name = true, bool add_brackets = true);
|
||||||
int32_t deviceRestore(JsonParserObject json);
|
int32_t deviceRestore(JsonParserObject json);
|
||||||
|
|
||||||
// Hue support
|
// Hue support
|
||||||
@ -753,11 +808,13 @@ public:
|
|||||||
|
|
||||||
// Append or clear attributes Json structure
|
// Append or clear attributes Json structure
|
||||||
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
|
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
|
||||||
|
static void jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string);
|
||||||
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
||||||
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
||||||
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
||||||
|
|
||||||
// Iterator
|
// Iterator
|
||||||
|
inline const LList<Z_Device> & getDevices(void) const { return _devices; }
|
||||||
size_t devicesSize(void) const {
|
size_t devicesSize(void) const {
|
||||||
return _devices.length();
|
return _devices.length();
|
||||||
}
|
}
|
||||||
|
@ -492,64 +492,71 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list
|
|||||||
device.attr_list.mergeList(attr_list);
|
device.attr_list.mergeList(attr_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal function to publish device information with respect to all `SetOption`s
|
||||||
|
//
|
||||||
|
void Z_Devices::jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string) {
|
||||||
|
const char * fname = zigbee_devices.getFriendlyName(device.shortaddr);
|
||||||
|
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||||
|
|
||||||
|
TasmotaGlobal.mqtt_data[0] = 0; // clear string
|
||||||
|
// Do we prefix with `ZbReceived`?
|
||||||
|
if (!Settings.flag4.remove_zbreceived) {
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
|
||||||
|
}
|
||||||
|
// What key do we use, shortaddr or name?
|
||||||
|
if (use_fname) {
|
||||||
|
Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, fname);
|
||||||
|
} else {
|
||||||
|
Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, device.shortaddr);
|
||||||
|
}
|
||||||
|
// Add "Device":"0x...."
|
||||||
|
ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), device.shortaddr);
|
||||||
|
// Add "Name":"xxx" if name is present
|
||||||
|
if (fname) {
|
||||||
|
ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(fname).c_str());
|
||||||
|
}
|
||||||
|
// Add all other attributes
|
||||||
|
ResponseAppend_P(PSTR("%s}}"), attr_list_string.c_str());
|
||||||
|
|
||||||
|
if (!Settings.flag4.remove_zbreceived) {
|
||||||
|
ResponseAppend_P(PSTR("}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.flag4.zigbee_distinct_topics) {
|
||||||
|
if (Settings.flag4.zb_topic_fname && fname) {
|
||||||
|
//Clean special characters and check size of friendly name
|
||||||
|
char stemp[TOPSZ];
|
||||||
|
strlcpy(stemp, (!strlen(fname)) ? MQTT_TOPIC : fname, sizeof(stemp));
|
||||||
|
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];
|
||||||
|
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), device.shortaddr);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (!device.valid()) { return; } // safeguard
|
if (!device.valid()) { return; } // safeguard
|
||||||
Z_attribute_list &attr_list = device.attr_list;
|
Z_attribute_list &attr_list = device.attr_list;
|
||||||
|
|
||||||
if (!attr_list.isEmpty()) {
|
if (!attr_list.isEmpty()) {
|
||||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
|
||||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
|
||||||
|
|
||||||
// save parameters is global variables to be used by Rules
|
// save parameters is global variables to be used by Rules
|
||||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||||
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
|
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
|
||||||
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
|
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
|
||||||
|
|
||||||
TasmotaGlobal.mqtt_data[0] = 0; // clear string
|
jsonPublishFlushAttrList(device, attr_list.toString());
|
||||||
// Do we prefix with `ZbReceived`?
|
|
||||||
if (!Settings.flag4.remove_zbreceived) {
|
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
|
|
||||||
}
|
|
||||||
// What key do we use, shortaddr or name?
|
|
||||||
if (use_fname) {
|
|
||||||
Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, fname);
|
|
||||||
} else {
|
|
||||||
Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, shortaddr);
|
|
||||||
}
|
|
||||||
// Add "Device":"0x...."
|
|
||||||
ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), shortaddr);
|
|
||||||
// Add "Name":"xxx" if name is present
|
|
||||||
if (fname) {
|
|
||||||
ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(fname).c_str());
|
|
||||||
}
|
|
||||||
// Add all other attributes
|
|
||||||
ResponseAppend_P(PSTR("%s}}"), attr_list.toString().c_str());
|
|
||||||
|
|
||||||
if (!Settings.flag4.remove_zbreceived) {
|
|
||||||
ResponseAppend_P(PSTR("}"));
|
|
||||||
}
|
|
||||||
attr_list.reset(); // clear the attributes
|
attr_list.reset(); // clear the attributes
|
||||||
|
|
||||||
if (Settings.flag4.zigbee_distinct_topics) {
|
|
||||||
if (Settings.flag4.zb_topic_fname && fname) {
|
|
||||||
//Clean special characters and check size of friendly name
|
|
||||||
char stemp[TOPSZ];
|
|
||||||
strlcpy(stemp, (!strlen(fname)) ? MQTT_TOPIC : fname, sizeof(stemp));
|
|
||||||
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];
|
|
||||||
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,20 +656,24 @@ String Z_Devices::dumpLightState(const Z_Device & device) {
|
|||||||
// Dump the internal memory of Zigbee devices
|
// Dump the internal memory of Zigbee devices
|
||||||
// Mode = 1: simple dump of devices addresses
|
// Mode = 1: simple dump of devices addresses
|
||||||
// Mode = 2: simple dump of devices addresses and names, endpoints, light
|
// Mode = 2: simple dump of devices addresses and names, endpoints, light
|
||||||
String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device) {
|
// Mode = 3: dump last known data attributes
|
||||||
|
// add_device_name : do we add shortaddr/name ?
|
||||||
|
String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name, bool add_brackets) {
|
||||||
uint16_t shortaddr = device.shortaddr;
|
uint16_t shortaddr = device.shortaddr;
|
||||||
char hex[22];
|
char hex[22];
|
||||||
|
|
||||||
Z_attribute_list attr_list;
|
Z_attribute_list attr_list;
|
||||||
|
|
||||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
if (add_device_name) {
|
||||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||||
|
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
||||||
|
|
||||||
if (device.friendlyName > 0) {
|
if (device.friendlyName > 0) {
|
||||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName);
|
attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (2 <= dump_mode) {
|
if (dump_mode >= 2) {
|
||||||
hex[0] = '0'; // prefix with '0x'
|
hex[0] = '0'; // prefix with '0x'
|
||||||
hex[1] = 'x';
|
hex[1] = 'x';
|
||||||
Uint64toHex(device.longaddr, &hex[2], 64);
|
Uint64toHex(device.longaddr, &hex[2], 64);
|
||||||
@ -695,7 +706,17 @@ String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & de
|
|||||||
}
|
}
|
||||||
attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str());
|
attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str());
|
||||||
}
|
}
|
||||||
return attr_list.toString(true);
|
if (dump_mode >= 3) {
|
||||||
|
// show internal data - mostly last known values
|
||||||
|
for (auto & data_elt : device.data) {
|
||||||
|
Z_Data_Type data_type = data_elt.getType();
|
||||||
|
|
||||||
|
data_elt.toAttributes(attr_list, data_type);
|
||||||
|
}
|
||||||
|
// add device wide attributes
|
||||||
|
device.toAttributes(attr_list);
|
||||||
|
}
|
||||||
|
return attr_list.toString(add_brackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If &device == nullptr, then dump all
|
// If &device == nullptr, then dump all
|
||||||
@ -796,9 +817,14 @@ Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) {
|
|||||||
* Export device specific attributes to ZbData
|
* Export device specific attributes to ZbData
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
void Z_Device::toAttributes(Z_attribute_list & attr_list) const {
|
void Z_Device::toAttributes(Z_attribute_list & attr_list) const {
|
||||||
if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); }
|
|
||||||
if (validBatteryPercent()) { attr_list.addAttribute(PSTR("BatteryPercentage")).setUInt(batterypercent); }
|
if (validBatteryPercent()) { attr_list.addAttribute(PSTR("BatteryPercentage")).setUInt(batterypercent); }
|
||||||
if (validLastSeen()) { attr_list.addAttribute(PSTR("LastSeen")).setUInt(last_seen); }
|
if (validLastSeen()) {
|
||||||
|
if (Rtc.utc_time >= last_seen) {
|
||||||
|
attr_list.addAttribute(PSTR("LastSeen")).setUInt(Rtc.utc_time - last_seen);
|
||||||
|
}
|
||||||
|
attr_list.addAttribute(PSTR("LastSeenEpoch")).setUInt(last_seen);
|
||||||
|
}
|
||||||
|
if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -194,14 +194,6 @@ class SBuffer hibernateDevices(void) {
|
|||||||
AddLog_P(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len);
|
AddLog_P(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) {
|
|
||||||
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbFlashStore %s"),
|
|
||||||
ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2));
|
|
||||||
free(hex_char);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,8 +293,8 @@ void hydrateDevices(const SBuffer &buf, uint32_t version) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dump = true, only dump to logs, don't actually load
|
||||||
void loadZigbeeDevices(void) {
|
void loadZigbeeDevices(bool dump_only = false) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// first copy SPI buffer into ram
|
// first copy SPI buffer into ram
|
||||||
uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len);
|
uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len);
|
||||||
@ -316,7 +308,7 @@ void loadZigbeeDevices(void) {
|
|||||||
Z_Flashentry flashdata;
|
Z_Flashentry flashdata;
|
||||||
memcpy_P(&flashdata, z_dev_start, sizeof(Z_Flashentry));
|
memcpy_P(&flashdata, z_dev_start, sizeof(Z_Flashentry));
|
||||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
|
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
|
||||||
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len);
|
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len);
|
||||||
|
|
||||||
// Check the signature
|
// Check the signature
|
||||||
if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2))
|
if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2))
|
||||||
@ -327,15 +319,21 @@ void loadZigbeeDevices(void) {
|
|||||||
SBuffer buf(buf_len);
|
SBuffer buf(buf_len);
|
||||||
buf.addBuffer(z_dev_start + sizeof(Z_Flashentry), buf_len);
|
buf.addBuffer(z_dev_start + sizeof(Z_Flashentry), buf_len);
|
||||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash v%d (%d bytes)"), version, buf_len);
|
AddLog_P(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));
|
if (dump_only) {
|
||||||
// Serial.printf("\n");
|
size_t buf_len = buf.len();
|
||||||
hydrateDevices(buf, version);
|
if (buf_len > 192) { buf_len = 192; }
|
||||||
zigbee_devices.clean(); // don't write back to Flash what we just loaded
|
AddLogBuffer(LOG_LEVEL_INFO, buf.getBuffer(), buf_len);
|
||||||
|
// Serial.printf(">> Buffer=");
|
||||||
|
// for (uint32_t i=0; i<buf.len(); i++) Serial.printf("%02X ", buf.get8(i));
|
||||||
|
// Serial.printf("\n");
|
||||||
|
} else {
|
||||||
|
hydrateDevices(buf, version);
|
||||||
|
zigbee_devices.clean(); // don't write back to Flash what we just loaded
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash"));
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash"));
|
||||||
}
|
}
|
||||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
free(spi_buffer);
|
free(spi_buffer);
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
|
185
tasmota/xdrv_23_zigbee_4a_eeprom.ino
Normal file
185
tasmota/xdrv_23_zigbee_4a_eeprom.ino
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
xdrv_23_zigbee_4a_eeprom.ino - zigbee support for Tasmota - saving configuration in I2C Eeprom of ZBBridge
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// ZbData v1
|
||||||
|
// 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 (excluding the length byte)
|
||||||
|
// uint16 - short address
|
||||||
|
//
|
||||||
|
// [Device specific data first]
|
||||||
|
// uint8 - length of structure (excluding the length byte)
|
||||||
|
// uint8[] - device wide data
|
||||||
|
//
|
||||||
|
// [Array of data structures]
|
||||||
|
// uint8 - length of structure
|
||||||
|
// uint8[] - list of data
|
||||||
|
//
|
||||||
|
|
||||||
|
void dumpZigbeeDevicesData(void) {
|
||||||
|
#ifdef USE_ZIGBEE_EZSP
|
||||||
|
if (zigbee.eeprom_present) {
|
||||||
|
SBuffer buf(192);
|
||||||
|
|
||||||
|
zigbee.eeprom.readBytes(64, 192, buf.getBuffer());
|
||||||
|
AddLogBuffer(LOG_LEVEL_INFO, buf.getBuffer(), 192);
|
||||||
|
}
|
||||||
|
#endif // USE_ZIGBEE_EZSP
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the lenght of consumed buffer, or -1 if error
|
||||||
|
int32_t hydrateDeviceWideData(class Z_Device & device, const SBuffer & buf, size_t start, size_t len) {
|
||||||
|
size_t segment_len = buf.get8(start);
|
||||||
|
if ((segment_len < 6) || (segment_len > len)) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid device wide data length=%d"), segment_len);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
device.last_seen = buf.get32(start+1);
|
||||||
|
device.lqi = buf.get8(start + 5);
|
||||||
|
device.batterypercent = buf.get8(start + 6);
|
||||||
|
return segment_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if success
|
||||||
|
bool hydrateDeviceData(class Z_Device & device, const SBuffer & buf, size_t start, size_t len) {
|
||||||
|
// First hydrate device wide data
|
||||||
|
int32_t ret = hydrateDeviceWideData(device, buf, start, len);
|
||||||
|
if (ret < 0) { return false; }
|
||||||
|
|
||||||
|
size_t offset = 0 + ret;
|
||||||
|
while (offset + 5 <= len) { // each entry is at least 5 bytes
|
||||||
|
uint8_t data_len = buf.get8(start + offset);
|
||||||
|
Z_Data & data_elt = device.data.createFromBuffer(buf, offset + 1, data_len);
|
||||||
|
offset += data_len + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative means error
|
||||||
|
// positive is the segment length
|
||||||
|
int32_t hydrateSingleDevice(const class SBuffer & buf, size_t start, size_t len) {
|
||||||
|
uint8_t segment_len = buf.get8(start);
|
||||||
|
if ((segment_len < 4) || (start + segment_len > len)) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid segment_len=%d"), segment_len);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// read shortaddr
|
||||||
|
uint16_t shortaddr = buf.get16(start + 1);
|
||||||
|
if (shortaddr >= 0xFFF0) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid shortaddr=0x%04X"), shortaddr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the device exists, if not skip the record
|
||||||
|
Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
|
||||||
|
if (&device != nullptr) {
|
||||||
|
|
||||||
|
// parse the rest
|
||||||
|
bool ret = hydrateDeviceData(device, buf, start + 3, segment_len - 3);
|
||||||
|
|
||||||
|
if (!ret) { return -1; }
|
||||||
|
}
|
||||||
|
return segment_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the entire blob
|
||||||
|
// return true if ok
|
||||||
|
bool hydrateDevicesDataBlob(const class SBuffer & buf, size_t start, size_t len) {
|
||||||
|
// read number of devices
|
||||||
|
uint8_t num_devices = buf.get8(start);
|
||||||
|
if (num_devices > 0x80) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "wrong number of devices=%d"), num_devices);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
for (uint32_t cur_dev_num = 0; (cur_dev_num < num_devices) && (offset + 4 <= len); cur_dev_num++) {
|
||||||
|
int32_t segment_len = hydrateSingleDevice(buf, offset, len);
|
||||||
|
|
||||||
|
// advance buffer
|
||||||
|
if (segment_len <= 0) { return false; }
|
||||||
|
offset += segment_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SBuffer hibernateDeviceData(const struct Z_Device & device, bool log = false) {
|
||||||
|
SBuffer buf(192);
|
||||||
|
|
||||||
|
// If we have zero information about the device, just skip ir
|
||||||
|
if (device.validLqi() ||
|
||||||
|
device.validBatteryPercent() ||
|
||||||
|
device.validLastSeen() ||
|
||||||
|
!device.data.isEmpty()) {
|
||||||
|
|
||||||
|
buf.add8(0x00); // overall length, will be updated later
|
||||||
|
buf.add16(device.shortaddr);
|
||||||
|
|
||||||
|
// device wide data
|
||||||
|
buf.add8(6); // 6 bytes
|
||||||
|
buf.add32(device.last_seen);
|
||||||
|
buf.add8(device.lqi);
|
||||||
|
buf.add8(device.batterypercent);
|
||||||
|
|
||||||
|
for (const auto & data_elt : device.data) {
|
||||||
|
size_t item_len = data_elt.DataTypeToLength(data_elt.getType());
|
||||||
|
buf.add8(item_len); // place-holder for length
|
||||||
|
buf.addBuffer((uint8_t*) &data_elt, item_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update overall length
|
||||||
|
buf.set8(0, buf.len() - 1);
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
size_t buf_len = buf.len() - 3;
|
||||||
|
char hex[2*buf_len + 1];
|
||||||
|
// skip first 3 bytes
|
||||||
|
ToHex_P(buf.buf(3), buf_len, hex, sizeof(hex));
|
||||||
|
|
||||||
|
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%s\"}"), device.shortaddr, hex);
|
||||||
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hibernateAllData(void) {
|
||||||
|
|
||||||
|
// first prefix is number of devices
|
||||||
|
uint8_t device_num = zigbee_devices.devicesSize();
|
||||||
|
|
||||||
|
for (const auto & device : zigbee_devices.getDevices()) {
|
||||||
|
// allocte a buffer for a single device
|
||||||
|
SBuffer buf = hibernateDeviceData(device, true); // log
|
||||||
|
if (buf.len() > 0) {
|
||||||
|
// TODO store in EEPROM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_ZIGBEE
|
@ -231,7 +231,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||||||
//{ Zmap8, Cx0005, 0x0004, (NameSupport), Cm1, 0 },
|
//{ Zmap8, Cx0005, 0x0004, (NameSupport), Cm1, 0 },
|
||||||
|
|
||||||
// On/off cluster
|
// On/off cluster
|
||||||
{ Zbool, Cx0006, 0x0000, Z_(Power), Cm1, 0 },
|
{ Zbool, Cx0006, 0x0000, Z_(Power), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_OnOff, power) },
|
||||||
{ Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), Cm1, 0 },
|
{ Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), Cm1, 0 },
|
||||||
{ Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280
|
{ Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280
|
||||||
|
|
||||||
@ -558,8 +558,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||||||
|
|
||||||
// IAS Cluster (Intruder Alarm System)
|
// IAS Cluster (Intruder Alarm System)
|
||||||
{ Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8)
|
{ Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8)
|
||||||
{ Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor
|
{ Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor
|
||||||
{ Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor
|
{ Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor
|
||||||
{ Zbool, Cx0500, 0xFFF0, Z_(Contact), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // We fit the first bit in the LSB
|
{ Zbool, Cx0500, 0xFFF0, Z_(Contact), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // We fit the first bit in the LSB
|
||||||
|
|
||||||
// Metering (Smart Energy) cluster
|
// Metering (Smart Energy) cluster
|
||||||
@ -1844,6 +1844,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||||||
switch (zigbee_type) {
|
switch (zigbee_type) {
|
||||||
case Zenum8:
|
case Zenum8:
|
||||||
case Zmap8:
|
case Zmap8:
|
||||||
|
case Zbool:
|
||||||
case Zuint8: *(uint8_t*)attr_address = uval32; break;
|
case Zuint8: *(uint8_t*)attr_address = uval32; break;
|
||||||
case Zenum16:
|
case Zenum16:
|
||||||
case Zmap16:
|
case Zmap16:
|
||||||
@ -1975,6 +1976,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||||
uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA;
|
uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA;
|
||||||
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
|
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
|
||||||
|
int8_t multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||||
Z_Data_Type map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
|
Z_Data_Type map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
|
||||||
uint8_t map_offset = (conv_mapping & 0x0F);
|
uint8_t map_offset = (conv_mapping & 0x0F);
|
||||||
|
|
||||||
@ -1982,7 +1984,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||||||
// we need to export this attribute
|
// we need to export this attribute
|
||||||
const char * conv_name = Z_strings + pgm_read_word(&converter->name_offset);
|
const char * conv_name = Z_strings + pgm_read_word(&converter->name_offset);
|
||||||
uint8_t zigbee_type = pgm_read_byte(&converter->type); // zigbee type to select right size 8/16/32 bits
|
uint8_t zigbee_type = pgm_read_byte(&converter->type); // zigbee type to select right size 8/16/32 bits
|
||||||
uint8_t *attr_address = ((uint8_t*)this) + sizeof(Z_Data) + map_offset; // address of attribute in memory
|
uint8_t * attr_address = ((uint8_t*)this) + sizeof(Z_Data) + map_offset; // address of attribute in memory
|
||||||
|
|
||||||
int32_t data_size = 0;
|
int32_t data_size = 0;
|
||||||
int32_t ival32;
|
int32_t ival32;
|
||||||
@ -1990,6 +1992,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||||||
switch (zigbee_type) {
|
switch (zigbee_type) {
|
||||||
case Zenum8:
|
case Zenum8:
|
||||||
case Zmap8:
|
case Zmap8:
|
||||||
|
case Zbool:
|
||||||
case Zuint8: uval32 = *(uint8_t*)attr_address; if (uval32 != 0xFF) data_size = 8; break;
|
case Zuint8: uval32 = *(uint8_t*)attr_address; if (uval32 != 0xFF) data_size = 8; break;
|
||||||
case Zmap16:
|
case Zmap16:
|
||||||
case Zenum16:
|
case Zenum16:
|
||||||
@ -2002,8 +2005,12 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||||||
if (data_size != 0) {
|
if (data_size != 0) {
|
||||||
Z_attribute & attr = attr_list.addAttribute(conv_name);
|
Z_attribute & attr = attr_list.addAttribute(conv_name);
|
||||||
|
|
||||||
if (data_size > 0) { attr.setUInt(uval32); }
|
float fval = (data_size > 0) ? uval32 : ival32;
|
||||||
else { attr.setInt(ival32); }
|
if ((1 != multiplier) && (0 != multiplier)) {
|
||||||
|
if (multiplier > 0) { fval = fval * multiplier; }
|
||||||
|
else { fval = fval / (-multiplier); }
|
||||||
|
}
|
||||||
|
attr.setFloat(fval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
|
|||||||
#endif // USE_ZIGBEE_EZSP
|
#endif // USE_ZIGBEE_EZSP
|
||||||
D_CMND_ZIGBEE_PERMITJOIN "|"
|
D_CMND_ZIGBEE_PERMITJOIN "|"
|
||||||
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|"
|
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|"
|
||||||
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
|
D_CMND_ZIGBEE_INFO "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
|
||||||
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
|
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
|
||||||
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|"
|
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|"
|
||||||
D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|"
|
D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|"
|
||||||
@ -46,7 +46,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
|
|||||||
#endif // USE_ZIGBEE_EZSP
|
#endif // USE_ZIGBEE_EZSP
|
||||||
&CmndZbPermitJoin,
|
&CmndZbPermitJoin,
|
||||||
&CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe,
|
&CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe,
|
||||||
&CmndZbForget, &CmndZbSave, &CmndZbName,
|
&CmndZbInfo, &CmndZbForget, &CmndZbSave, &CmndZbName,
|
||||||
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
|
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
|
||||||
&CmndZbLight, &CmndZbOccupancy,
|
&CmndZbLight, &CmndZbOccupancy,
|
||||||
&CmndZbRestore, &CmndZbBindState, &CmndZbMap,
|
&CmndZbRestore, &CmndZbBindState, &CmndZbMap,
|
||||||
@ -94,6 +94,16 @@ void ZigbeeInit(void)
|
|||||||
#endif
|
#endif
|
||||||
SettingsSave(2);
|
SettingsSave(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_ZIGBEE_EZSP
|
||||||
|
// Check the I2C EEprom
|
||||||
|
Wire.beginTransmission(USE_ZIGBEE_ZBBRIDGE_EEPROM);
|
||||||
|
uint8_t error = Wire.endTransmission();
|
||||||
|
if (0 == error) {
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZBBridge EEPROM found at address 0x%02X"), USE_ZIGBEE_ZBBRIDGE_EEPROM);
|
||||||
|
zigbee.eeprom_present = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// update commands with the current settings
|
// update commands with the current settings
|
||||||
@ -1175,13 +1185,43 @@ void CmndZbForget(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Command `ZbInfo`
|
||||||
|
// Display all information known about a device, this equivalent to `2bStatus3` with a simpler JSON output
|
||||||
|
//
|
||||||
|
void CmndZbInfo(void) {
|
||||||
|
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||||
|
Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
|
||||||
|
if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
|
||||||
|
|
||||||
|
// everything is good, we can send the command
|
||||||
|
|
||||||
|
String device_info = Z_Devices::dumpSingleDevice(3, device, false, false);
|
||||||
|
Z_Devices::jsonPublishFlushAttrList(device, device_info);
|
||||||
|
|
||||||
|
ResponseCmndDone();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Command `ZbSave`
|
// Command `ZbSave`
|
||||||
// Save Zigbee information to flash
|
// Save Zigbee information to flash
|
||||||
//
|
//
|
||||||
void CmndZbSave(void) {
|
void CmndZbSave(void) {
|
||||||
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||||
saveZigbeeDevices();
|
switch (XdrvMailbox.payload) {
|
||||||
|
case 2: // save only data
|
||||||
|
hibernateAllData();
|
||||||
|
break;
|
||||||
|
case -1: // dump configuration
|
||||||
|
loadZigbeeDevices(true); // dump only
|
||||||
|
break;
|
||||||
|
case -2: // dump data
|
||||||
|
dumpZigbeeDevicesData();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
saveZigbeeDevices();
|
||||||
|
break;
|
||||||
|
}
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1378,196 +1418,32 @@ void CmndZbStatus(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Innder part of ZbData parsing
|
|
||||||
//
|
|
||||||
// {"L02":{"Dimmer":10,"Sat":254}}
|
|
||||||
bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) {
|
|
||||||
for (auto data_elt : root) {
|
|
||||||
// Parse key in format "L02":....
|
|
||||||
const char * data_type_str = data_elt.getStr();
|
|
||||||
Z_Data_Type data_type;
|
|
||||||
uint8_t endpoint;
|
|
||||||
uint8_t config = 0xFF; // unspecified
|
|
||||||
|
|
||||||
// parse key in the form "L01.5"
|
|
||||||
if (!Z_Data::ConfigToZData(data_type_str, &data_type, &endpoint, &config)) { data_type = Z_Data_Type::Z_Unknown; }
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
JsonParserToken val;
|
|
||||||
if (data_type == 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(); }
|
|
||||||
} else {
|
|
||||||
// Import generic attributes first
|
|
||||||
device.addEndpoint(endpoint);
|
|
||||||
Z_Data & data = device.data.getByType(data_type, endpoint);
|
|
||||||
|
|
||||||
// scan through attributes
|
|
||||||
if (&data != nullptr) {
|
|
||||||
if (config != 0xFF) {
|
|
||||||
data.setConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto attr : data_values) {
|
|
||||||
JsonParserToken attr_value = attr.getValue();
|
|
||||||
uint8_t conv_zigbee_type;
|
|
||||||
Z_Data_Type conv_data_type;
|
|
||||||
uint8_t conv_map_offset;
|
|
||||||
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_P(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
case Z_Data_Type::Z_OnOff:
|
|
||||||
{
|
|
||||||
Z_Data_OnOff & onoff = (Z_Data_OnOff&) data;
|
|
||||||
|
|
||||||
if (val = data_values[PSTR("Power")]) { onoff.setPower(val.getUInt() ? true : false); }
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Command `ZbData`
|
// Command `ZbData`
|
||||||
//
|
//
|
||||||
void CmndZbData(void) {
|
void CmndZbData(void) {
|
||||||
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||||
RemoveSpace(XdrvMailbox.data);
|
|
||||||
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
|
// check if parameters contain a comma ','
|
||||||
JsonParserToken zbdata = root.getObject().findStartsWith(PSTR("ZbData"));
|
char *p;
|
||||||
if (zbdata) {
|
char *str = strtok_r(XdrvMailbox.data, ",", &p);
|
||||||
root = zbdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto device_name : root) {
|
// parse first part, <device_id>
|
||||||
Z_Device & device = zigbee_devices.parseDeviceFromName(device_name.getStr(), true);
|
Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
|
||||||
if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
|
if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
|
||||||
JsonParserObject inner_data = device_name.getValue().getObject();
|
|
||||||
if (inner_data) {
|
if (p) {
|
||||||
if (!parseDeviceInnerData(device, inner_data)) {
|
// set ZbData
|
||||||
return;
|
const SBuffer buf = SBuffer::SBufferFromHex(p, strlen(p));
|
||||||
}
|
hydrateDeviceData(device, buf, 0, buf.len());
|
||||||
}
|
|
||||||
}
|
|
||||||
zigbee_devices.dirty(); // save to flash
|
|
||||||
ResponseCmndDone();
|
|
||||||
} else {
|
} else {
|
||||||
// non-JSON, export current data
|
// non-JSON, export current data
|
||||||
// ZbData 0x1234
|
// ZbData 0x1234
|
||||||
// ZbData Device_Name
|
// ZbData Device_Name
|
||||||
Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true);
|
hibernateDeviceData(device, true); // log
|
||||||
if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
|
|
||||||
|
|
||||||
Z_attribute_list attr_data;
|
|
||||||
|
|
||||||
{ // scope to force object deallocation
|
|
||||||
Z_attribute_list device_attr;
|
|
||||||
device.toAttributes(device_attr);
|
|
||||||
attr_data.addAttribute(F("_")).setStrRaw(device_attr.toString(true).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate on data objects
|
|
||||||
for (auto & data_elt : device.data) {
|
|
||||||
Z_attribute_list inner_attr;
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
Z_Data_Type data_type = data_elt.getType();
|
|
||||||
key[0] = Z_Data::DataTypeToChar(data_type);
|
|
||||||
switch (data_type) {
|
|
||||||
case Z_Data_Type::Z_Plug:
|
|
||||||
((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
case Z_Data_Type::Z_Light:
|
|
||||||
((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
case Z_Data_Type::Z_OnOff:
|
|
||||||
((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
case Z_Data_Type::Z_Thermo:
|
|
||||||
((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
case Z_Data_Type::Z_Alarm:
|
|
||||||
((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
case Z_Data_Type::Z_PIR:
|
|
||||||
((Z_Data_PIR&)data_elt).toAttributes(inner_attr, data_type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((key[0] != '\0') && (key[0] != '?')) {
|
|
||||||
attr_data.addAttribute(key).setStrRaw(inner_attr.toString(true).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char hex[8];
|
|
||||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), device.shortaddr);
|
|
||||||
Response_P(PSTR("{\"%s\":{\"%s\":%s}}"), XdrvMailbox.command, hex, attr_data.toString(true).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -1652,6 +1528,8 @@ void CmndZbConfig(void) {
|
|||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
// comparator function used to sort Zigbee devices by alphabetical order (if friendlyname)
|
||||||
|
// then by shortaddr if they don't have friendlyname
|
||||||
int device_cmp(const void * a, const void * b) {
|
int device_cmp(const void * a, const void * b) {
|
||||||
const Z_Device &dev_a = zigbee_devices.devicesAt(*(uint8_t*)a);
|
const Z_Device &dev_a = zigbee_devices.devicesAt(*(uint8_t*)a);
|
||||||
const Z_Device &dev_b = zigbee_devices.devicesAt(*(uint8_t*)b);
|
const Z_Device &dev_b = zigbee_devices.devicesAt(*(uint8_t*)b);
|
||||||
@ -1664,7 +1542,7 @@ extern "C" {
|
|||||||
return (int32_t)dev_a.shortaddr - (int32_t)dev_b.shortaddr;
|
return (int32_t)dev_a.shortaddr - (int32_t)dev_b.shortaddr;
|
||||||
} else {
|
} else {
|
||||||
if (fn_a) return -1;
|
if (fn_a) return -1;
|
||||||
return 1;
|
else return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1829,15 +1707,16 @@ void ZigbeeShow(bool json)
|
|||||||
|
|
||||||
// Light, switches and plugs
|
// Light, switches and plugs
|
||||||
const Z_Data_OnOff & onoff = device.data.find<Z_Data_OnOff>();
|
const Z_Data_OnOff & onoff = device.data.find<Z_Data_OnOff>();
|
||||||
|
bool onoff_display = (&onoff != nullptr) ? onoff.validPower() : false;
|
||||||
const Z_Data_Light & light = device.data.find<Z_Data_Light>();
|
const Z_Data_Light & light = device.data.find<Z_Data_Light>();
|
||||||
bool light_display = (&light != nullptr) ? light.validDimmer() : false;
|
bool light_display = (&light != nullptr) ? light.validDimmer() : false;
|
||||||
const Z_Data_Plug & plug = device.data.find<Z_Data_Plug>();
|
const Z_Data_Plug & plug = device.data.find<Z_Data_Plug>();
|
||||||
if ((&onoff != nullptr) || light_display || (&plug != nullptr)) {
|
if (onoff_display || light_display || (&plug != nullptr)) {
|
||||||
int8_t channels = device.getLightChannels();
|
int8_t channels = device.getLightChannels();
|
||||||
if (channels < 0) { channels = 5; } // if number of channel is unknown, display all known attributes
|
if (channels < 0) { channels = 5; } // if number of channel is unknown, display all known attributes
|
||||||
WSContentSend_P(PSTR("<tr class='htr'><td colspan=\"4\">┆"));
|
WSContentSend_P(PSTR("<tr class='htr'><td colspan=\"4\">┆"));
|
||||||
if (&onoff != nullptr) {
|
if (onoff_display) {
|
||||||
WSContentSend_P(PSTR(" %s"), device.getPower() ? PSTR(D_ON) : PSTR(D_OFF));
|
WSContentSend_P(PSTR(" %s"), onoff.getPower() ? PSTR(D_ON) : PSTR(D_OFF));
|
||||||
}
|
}
|
||||||
if (&light != nullptr) {
|
if (&light != nullptr) {
|
||||||
if (light.validDimmer() && (channels >= 1)) {
|
if (light.validDimmer() && (channels >= 1)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user