mirror of https://github.com/arendst/Tasmota.git
Merge pull request #9810 from s-hadinger/zigbee_zbinfo
Zigbee command ``ZbInfo`` and prepare support for EEPROM
This commit is contained in:
commit
4824132413
|
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Zigbee alarm persistence (#9785)
|
||||
- Support for EZO PMP sensors by Christopher Tremblay (#9760)
|
||||
- Commands ``TuyaRGB``, ``TuyaEnum`` and ``TuyaEnumList`` (#9769)
|
||||
- Zigbee command ``ZbInfo`` and prepare support for EEPROM
|
||||
|
||||
### Changed
|
||||
- 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;
|
||||
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_CMND_ZIGBEE_PROBE "Probe"
|
||||
#define D_CMND_ZIGBEE_FORGET "Forget"
|
||||
#define D_CMND_ZIGBEE_INFO "Info"
|
||||
#define D_CMND_ZIGBEE_SAVE "Save"
|
||||
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
||||
#define D_CMND_ZIGBEE_CLUSTER "Cluster"
|
||||
|
|
|
@ -729,7 +729,8 @@
|
|||
#define USE_ZIGBEE_COALESCE_ATTR_TIMER 350 // timer to coalesce attribute values (in ms)
|
||||
#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_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 -----------------------
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#ifdef USE_ZIGBEE_EZSP
|
||||
#include "Eeprom24C512.h"
|
||||
#endif // USE_ZIGBEE_EZSP
|
||||
|
||||
// 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_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 state_machine = false; // the state machine is running
|
||||
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 init_phase = true; // initialization phase, before accepting zigbee traffic
|
||||
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_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
|
||||
|
||||
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;
|
||||
SBuffer *zigbee_buffer = nullptr;
|
||||
|
|
|
@ -53,7 +53,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = {
|
|||
'\0', // 0x0C
|
||||
'\0', // 0x0D
|
||||
'\0', // 0x0E
|
||||
'E', // 0x05 Z_Data_Type::Z_Ext
|
||||
'E', // 0x0F Z_Data_Type::Z_Ext
|
||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||
};
|
||||
|
||||
|
@ -63,12 +63,13 @@ class Z_Data_Set;
|
|||
\*********************************************************************************************/
|
||||
class Z_Data {
|
||||
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 int8_t getConfig(void) const { return _config; }
|
||||
inline bool validConfig(void) const { return _config != 0xF; }
|
||||
inline void setConfig(int8_t config) { _config = config; }
|
||||
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; }
|
||||
|
||||
|
@ -79,6 +80,7 @@ public:
|
|||
inline bool update(void) { return false; }
|
||||
|
||||
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 Z_Data_Type CharToDataType(char c);
|
||||
|
@ -87,9 +89,10 @@ public:
|
|||
friend class Z_Data_Set;
|
||||
protected:
|
||||
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 _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) {
|
||||
|
@ -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 {
|
||||
public:
|
||||
Z_Data_OnOff(uint8_t endpoint = 0) :
|
||||
Z_Data(Z_Data_Type::Z_OnOff, endpoint)
|
||||
{
|
||||
_config = 1; // at least 1 OnOff
|
||||
}
|
||||
Z_Data(Z_Data_Type::Z_OnOff, endpoint),
|
||||
power(0xFF)
|
||||
{}
|
||||
|
||||
inline bool validPower(uint32_t relay = 0) const { return (_config > relay); } // power is declared
|
||||
inline bool getPower(uint32_t relay = 0) const { return bitRead(_power, relay); }
|
||||
void setPower(bool val, uint32_t relay = 0);
|
||||
|
||||
void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const;
|
||||
inline bool validPower(void) const { return 0xFF != power; } // power is declared
|
||||
inline bool getPower(void) const { return validPower() ? (power != 0) : false; } // default to false if undefined
|
||||
inline void setPower(bool val) { power = val ? 1 : 0; }
|
||||
|
||||
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
|
||||
\*********************************************************************************************/
|
||||
|
@ -430,6 +425,37 @@ public:
|
|||
// 0x0226 Glass break sensor
|
||||
// 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
|
||||
|
@ -453,7 +479,10 @@ public:
|
|||
template <class M>
|
||||
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>
|
||||
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>
|
||||
M & Z_Data_Set::get(uint8_t 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;
|
||||
}
|
||||
|
||||
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
|
||||
\*********************************************************************************************/
|
||||
|
@ -561,11 +614,13 @@ public:
|
|||
|
||||
// New version of device data handling
|
||||
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 batterypercent; // battery percentage (0..100), 0xFF means unknwon
|
||||
// power plug data-
|
||||
uint32_t last_seen; // Last seen time (epoch)
|
||||
uint16_t reserved_for_alignment;
|
||||
// END OF DEVICE WIDE DATA
|
||||
|
||||
// Constructor with all defaults
|
||||
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
||||
|
@ -736,7 +791,7 @@ public:
|
|||
// Dump json
|
||||
static String dumpLightState(const Z_Device & device);
|
||||
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);
|
||||
|
||||
// Hue support
|
||||
|
@ -753,11 +808,13 @@ public:
|
|||
|
||||
// Append or clear attributes Json structure
|
||||
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
|
||||
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
||||
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
||||
|
||||
// Iterator
|
||||
inline const LList<Z_Device> & getDevices(void) const { return _devices; }
|
||||
size_t devicesSize(void) const {
|
||||
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);
|
||||
}
|
||||
|
||||
//
|
||||
// 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) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
if (!device.valid()) { return; } // safeguard
|
||||
Z_attribute_list &attr_list = device.attr_list;
|
||||
|
||||
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
|
||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
|
||||
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
|
||||
|
||||
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, 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("}"));
|
||||
}
|
||||
jsonPublishFlushAttrList(device, attr_list.toString());
|
||||
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
|
||||
// Mode = 1: simple dump of devices addresses
|
||||
// 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;
|
||||
char hex[22];
|
||||
|
||||
Z_attribute_list attr_list;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
||||
if (add_device_name) {
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
||||
|
||||
if (device.friendlyName > 0) {
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName);
|
||||
if (device.friendlyName > 0) {
|
||||
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[1] = 'x';
|
||||
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());
|
||||
}
|
||||
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
|
||||
|
@ -796,9 +817,14 @@ Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) {
|
|||
* Export device specific attributes to ZbData
|
||||
\*********************************************************************************************/
|
||||
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 (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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -301,8 +293,8 @@ void hydrateDevices(const SBuffer &buf, uint32_t version) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void loadZigbeeDevices(void) {
|
||||
// dump = true, only dump to logs, don't actually load
|
||||
void loadZigbeeDevices(bool dump_only = false) {
|
||||
#ifdef ESP32
|
||||
// first copy SPI buffer into ram
|
||||
uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len);
|
||||
|
@ -316,7 +308,7 @@ void loadZigbeeDevices(void) {
|
|||
Z_Flashentry flashdata;
|
||||
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 "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
|
||||
if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2))
|
||||
|
@ -327,15 +319,21 @@ void loadZigbeeDevices(void) {
|
|||
SBuffer buf(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);
|
||||
// Serial.printf(">> Buffer=");
|
||||
// for (uint32_t i=0; i<buf.len(); i++) Serial.printf("%02X ", buf.get8(i));
|
||||
// Serial.printf("\n");
|
||||
hydrateDevices(buf, version);
|
||||
zigbee_devices.clean(); // don't write back to Flash what we just loaded
|
||||
|
||||
if (dump_only) {
|
||||
size_t buf_len = buf.len();
|
||||
if (buf_len > 192) { buf_len = 192; }
|
||||
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 {
|
||||
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
|
||||
free(spi_buffer);
|
||||
#endif // ESP32
|
||||
|
|
|
@ -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 },
|
||||
|
||||
// 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 },
|
||||
{ Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280
|
||||
|
||||
|
@ -558,8 +558,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||
|
||||
// IAS Cluster (Intruder Alarm System)
|
||||
{ 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
|
||||
{ Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status 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_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
|
||||
|
||||
// Metering (Smart Energy) cluster
|
||||
|
@ -1844,6 +1844,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||
switch (zigbee_type) {
|
||||
case Zenum8:
|
||||
case Zmap8:
|
||||
case Zbool:
|
||||
case Zuint8: *(uint8_t*)attr_address = uval32; break;
|
||||
case Zenum16:
|
||||
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];
|
||||
uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA;
|
||||
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);
|
||||
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
|
||||
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 *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 ival32;
|
||||
|
@ -1990,6 +1992,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||
switch (zigbee_type) {
|
||||
case Zenum8:
|
||||
case Zmap8:
|
||||
case Zbool:
|
||||
case Zuint8: uval32 = *(uint8_t*)attr_address; if (uval32 != 0xFF) data_size = 8; break;
|
||||
case Zmap16:
|
||||
case Zenum16:
|
||||
|
@ -2002,8 +2005,12 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const
|
|||
if (data_size != 0) {
|
||||
Z_attribute & attr = attr_list.addAttribute(conv_name);
|
||||
|
||||
if (data_size > 0) { attr.setUInt(uval32); }
|
||||
else { attr.setInt(ival32); }
|
||||
float fval = (data_size > 0) ? uval32 : 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
|
||||
D_CMND_ZIGBEE_PERMITJOIN "|"
|
||||
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_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|"
|
||||
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
|
||||
&CmndZbPermitJoin,
|
||||
&CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe,
|
||||
&CmndZbForget, &CmndZbSave, &CmndZbName,
|
||||
&CmndZbInfo, &CmndZbForget, &CmndZbSave, &CmndZbName,
|
||||
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
|
||||
&CmndZbLight, &CmndZbOccupancy,
|
||||
&CmndZbRestore, &CmndZbBindState, &CmndZbMap,
|
||||
|
@ -94,6 +94,16 @@ void ZigbeeInit(void)
|
|||
#endif
|
||||
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
|
||||
|
@ -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`
|
||||
// Save Zigbee information to flash
|
||||
//
|
||||
void CmndZbSave(void) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
//
|
||||
void CmndZbData(void) {
|
||||
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
|
||||
JsonParserToken zbdata = root.getObject().findStartsWith(PSTR("ZbData"));
|
||||
if (zbdata) {
|
||||
root = zbdata;
|
||||
}
|
||||
// check if parameters contain a comma ','
|
||||
char *p;
|
||||
char *str = strtok_r(XdrvMailbox.data, ",", &p);
|
||||
|
||||
for (auto device_name : root) {
|
||||
Z_Device & device = zigbee_devices.parseDeviceFromName(device_name.getStr(), true);
|
||||
if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
|
||||
JsonParserObject inner_data = device_name.getValue().getObject();
|
||||
if (inner_data) {
|
||||
if (!parseDeviceInnerData(device, inner_data)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
zigbee_devices.dirty(); // save to flash
|
||||
ResponseCmndDone();
|
||||
// parse first part, <device_id>
|
||||
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 (p) {
|
||||
// set ZbData
|
||||
const SBuffer buf = SBuffer::SBufferFromHex(p, strlen(p));
|
||||
hydrateDeviceData(device, buf, 0, buf.len());
|
||||
} else {
|
||||
// non-JSON, export current data
|
||||
// ZbData 0x1234
|
||||
// ZbData Device_Name
|
||||
Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true);
|
||||
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());
|
||||
hibernateDeviceData(device, true); // log
|
||||
}
|
||||
|
||||
ResponseCmndDone();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -1652,6 +1528,8 @@ void CmndZbConfig(void) {
|
|||
\*********************************************************************************************/
|
||||
|
||||
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) {
|
||||
const Z_Device &dev_a = zigbee_devices.devicesAt(*(uint8_t*)a);
|
||||
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;
|
||||
} else {
|
||||
if (fn_a) return -1;
|
||||
return 1;
|
||||
else return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1829,15 +1707,16 @@ void ZigbeeShow(bool json)
|
|||
|
||||
// Light, switches and plugs
|
||||
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>();
|
||||
bool light_display = (&light != nullptr) ? light.validDimmer() : false;
|
||||
const Z_Data_Plug & plug = device.data.find<Z_Data_Plug>();
|
||||
if ((&onoff != nullptr) || light_display || (&plug != nullptr)) {
|
||||
if (onoff_display || light_display || (&plug != nullptr)) {
|
||||
int8_t channels = device.getLightChannels();
|
||||
if (channels < 0) { channels = 5; } // if number of channel is unknown, display all known attributes
|
||||
WSContentSend_P(PSTR("<tr class='htr'><td colspan=\"4\">┆"));
|
||||
if (&onoff != nullptr) {
|
||||
WSContentSend_P(PSTR(" %s"), device.getPower() ? PSTR(D_ON) : PSTR(D_OFF));
|
||||
if (onoff_display) {
|
||||
WSContentSend_P(PSTR(" %s"), onoff.getPower() ? PSTR(D_ON) : PSTR(D_OFF));
|
||||
}
|
||||
if (&light != nullptr) {
|
||||
if (light.validDimmer() && (channels >= 1)) {
|
||||
|
|
Loading…
Reference in New Issue