mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-25 19:56:30 +00:00
Merge branch 'development' into release
This commit is contained in:
commit
bafd4cd90a
@ -67,7 +67,9 @@ See [wiki migration path](https://tasmota.github.io/docs/#/Upgrading?id=migratio
|
|||||||
3. Migrate to **Sonoff-Tasmota 5.14**
|
3. Migrate to **Sonoff-Tasmota 5.14**
|
||||||
4. Migrate to **Sonoff-Tasmota 6.x**
|
4. Migrate to **Sonoff-Tasmota 6.x**
|
||||||
5. Migrate to **Tasmota 7.x**
|
5. Migrate to **Tasmota 7.x**
|
||||||
|
|
||||||
--- Major change in parameter storage layout ---
|
--- Major change in parameter storage layout ---
|
||||||
|
|
||||||
6. Migrate to **Tasmota 8.1**
|
6. Migrate to **Tasmota 8.1**
|
||||||
7. Migrate to **Tasmota 8.x**
|
7. Migrate to **Tasmota 8.x**
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ See [migration path](https://tasmota.github.io/docs/#/Upgrading?id=migration-pat
|
|||||||
3. Migrate to **Sonoff-Tasmota 5.14**
|
3. Migrate to **Sonoff-Tasmota 5.14**
|
||||||
4. Migrate to **Sonoff-Tasmota 6.x**
|
4. Migrate to **Sonoff-Tasmota 6.x**
|
||||||
5. Migrate to **Tasmota 7.x**
|
5. Migrate to **Tasmota 7.x**
|
||||||
|
|
||||||
--- Major change in parameter storage layout ---
|
--- Major change in parameter storage layout ---
|
||||||
|
|
||||||
6. Migrate to **Tasmota 8.1**
|
6. Migrate to **Tasmota 8.1**
|
||||||
7. Migrate to **Tasmota 8.x**
|
7. Migrate to **Tasmota 8.x**
|
||||||
|
|
||||||
@ -56,4 +58,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
|
|||||||
- Add support for max 150 characters in most command parameter strings (#3686, #4754)
|
- Add support for max 150 characters in most command parameter strings (#3686, #4754)
|
||||||
- Add support for GPS as NTP server by Christian Baars and Adrian Scillato
|
- Add support for GPS as NTP server by Christian Baars and Adrian Scillato
|
||||||
- Add Zigbee coalesce sensor attributes into a single message
|
- Add Zigbee coalesce sensor attributes into a single message
|
||||||
|
- Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor
|
||||||
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300
|
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
### 8.0.0.2 20191223
|
### 8.0.0.2 20191223
|
||||||
|
|
||||||
- Changed Settings variable namings
|
- Changed Settings variable namings
|
||||||
|
- Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor
|
||||||
|
|
||||||
### 8.0.0.1 20191221
|
### 8.0.0.1 20191221
|
||||||
|
|
||||||
|
@ -468,6 +468,7 @@
|
|||||||
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
|
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
|
||||||
#define D_CMND_ZIGBEE_RESET "ZigbeeReset"
|
#define D_CMND_ZIGBEE_RESET "ZigbeeReset"
|
||||||
#define D_JSON_ZIGBEE_CC2530 "CC2530"
|
#define D_JSON_ZIGBEE_CC2530 "CC2530"
|
||||||
|
#define D_CMND_ZIGBEEZNPRECEIVE "ZigbeeZNPReceive" // only for debug
|
||||||
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
|
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
|
||||||
#define D_JSON_ZIGBEE_STATE "ZigbeeState"
|
#define D_JSON_ZIGBEE_STATE "ZigbeeState"
|
||||||
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
|
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
|
||||||
|
@ -460,7 +460,9 @@ void RtcSetTime(uint32_t epoch)
|
|||||||
if (epoch < START_VALID_TIME) { // 2016-01-01
|
if (epoch < START_VALID_TIME) { // 2016-01-01
|
||||||
Rtc.user_time_entry = false;
|
Rtc.user_time_entry = false;
|
||||||
ntp_force_sync = true;
|
ntp_force_sync = true;
|
||||||
|
sntp_init();
|
||||||
} else {
|
} else {
|
||||||
|
sntp_stop();
|
||||||
Rtc.user_time_entry = true;
|
Rtc.user_time_entry = true;
|
||||||
Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond
|
Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,8 @@ public:
|
|||||||
void jsonClear(uint16_t shortaddr);
|
void jsonClear(uint16_t shortaddr);
|
||||||
void jsonAppend(uint16_t shortaddr, JsonObject &values);
|
void jsonAppend(uint16_t shortaddr, JsonObject &values);
|
||||||
const JsonObject *jsonGet(uint16_t shortaddr);
|
const JsonObject *jsonGet(uint16_t shortaddr);
|
||||||
|
const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer
|
||||||
|
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Z_Device> _devices = {};
|
std::vector<Z_Device> _devices = {};
|
||||||
@ -419,6 +421,67 @@ void Z_Devices::jsonClear(uint16_t shortaddr) {
|
|||||||
device.json_buffer->clear();
|
device.json_buffer->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
|
||||||
|
to.remove(key); // force remove to have metadata like LinkQuality at the end
|
||||||
|
|
||||||
|
if (val.is<char*>()) {
|
||||||
|
String sval = val.as<String>(); // force a copy of the String value
|
||||||
|
to.set(key, sval);
|
||||||
|
} else if (val.is<JsonArray>()) {
|
||||||
|
JsonArray &nested_arr = to.createNestedArray(key);
|
||||||
|
CopyJsonArray(nested_arr, val.as<JsonArray>());
|
||||||
|
} else if (val.is<JsonObject>()) {
|
||||||
|
JsonObject &nested_obj = to.createNestedObject(key);
|
||||||
|
CopyJsonObject(nested_obj, val.as<JsonObject>());
|
||||||
|
} else {
|
||||||
|
to.set(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
|
||||||
|
for (auto v : arr) {
|
||||||
|
if (v.is<char*>()) {
|
||||||
|
String sval = v.as<String>(); // force a copy of the String value
|
||||||
|
to.add(sval);
|
||||||
|
} else if (v.is<JsonArray>()) {
|
||||||
|
} else if (v.is<JsonObject>()) {
|
||||||
|
} else {
|
||||||
|
to.add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
|
||||||
|
for (auto kv : from) {
|
||||||
|
String key_string = kv.key;
|
||||||
|
JsonVariant &val = kv.value;
|
||||||
|
|
||||||
|
CopyJsonVariant(to, key_string, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
|
||||||
|
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
|
||||||
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
|
if (&device == nullptr) { return false; } // don't crash if not found
|
||||||
|
if (&values == nullptr) { return false; }
|
||||||
|
|
||||||
|
if (nullptr == device.json) {
|
||||||
|
return false; // if no previous value, no conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto kv : values) {
|
||||||
|
String key_string = kv.key;
|
||||||
|
|
||||||
|
if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
|
||||||
|
if (device.json->containsKey(kv.key)) {
|
||||||
|
return true; // conflict!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) {
|
void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
@ -428,24 +491,7 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) {
|
|||||||
device.json = &(device.json_buffer->createObject());
|
device.json = &(device.json_buffer->createObject());
|
||||||
}
|
}
|
||||||
// copy all values from 'values' to 'json'
|
// copy all values from 'values' to 'json'
|
||||||
for (auto kv : values) {
|
CopyJsonObject(*device.json, values);
|
||||||
String key_string = kv.key;
|
|
||||||
const char * key = key_string.c_str();
|
|
||||||
JsonVariant &val = kv.value;
|
|
||||||
|
|
||||||
device.json->remove(key_string); // force remove to have metadata like LinkQuality at the end
|
|
||||||
|
|
||||||
if (val.is<char*>()) {
|
|
||||||
String sval = val.as<String>(); // force a copy of the String value
|
|
||||||
device.json->set(key_string, sval);
|
|
||||||
} else if (val.is<JsonArray>()) {
|
|
||||||
// todo
|
|
||||||
} else if (val.is<JsonObject>()) {
|
|
||||||
// todo
|
|
||||||
} else {
|
|
||||||
device.json->set(key_string, kv.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
||||||
@ -454,6 +500,19 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
|||||||
return device.json;
|
return device.json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const void Z_Devices::jsonPublish(uint16_t shortaddr) {
|
||||||
|
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
||||||
|
if (json == nullptr) { return; } // don't crash if not found
|
||||||
|
|
||||||
|
String msg = "";
|
||||||
|
json->printTo(msg);
|
||||||
|
zigbee_devices.jsonClear(shortaddr);
|
||||||
|
Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
||||||
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
||||||
|
XdrvRulesProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Dump the internal memory of Zigbee devices
|
// Dump the internal memory of Zigbee devices
|
||||||
// Mode = 1: simple dump of devices addresses and names
|
// Mode = 1: simple dump of devices addresses and names
|
||||||
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters
|
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters
|
||||||
|
@ -100,6 +100,7 @@ public:
|
|||||||
return _frame_control.b.frame_type & 1;
|
return _frame_control.b.frame_type & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len);
|
||||||
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
|
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
|
||||||
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
|
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
|
||||||
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
|
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
|
||||||
@ -290,17 +291,20 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
bool parse_as_string = true;
|
bool parse_as_string = true;
|
||||||
uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
|
uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
|
||||||
i += (attrtype <= 0x42) ? 1 : 2; // increment pointer
|
i += (attrtype <= 0x42) ? 1 : 2; // increment pointer
|
||||||
|
if (i + len > buf.len()) { // make sure we don't get past the buffer
|
||||||
|
len = buf.len() - i;
|
||||||
|
}
|
||||||
|
|
||||||
// check if we can safely use a string
|
// check if we can safely use a string
|
||||||
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
|
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
|
||||||
else {
|
// else {
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
// for (uint32_t j = 0; j < len; j++) {
|
||||||
if (0x00 == buf.get8(i+j)) {
|
// if (0x00 == buf.get8(i+j)) {
|
||||||
parse_as_string = false;
|
// parse_as_string = false;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (parse_as_string) {
|
if (parse_as_string) {
|
||||||
char str[len+1];
|
char str[len+1];
|
||||||
@ -409,19 +413,28 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
return i - offset; // how much have we increased the index
|
return i - offset; // how much have we increased the index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate an attribute name based on cluster number, attribute, and suffix if duplicates
|
||||||
|
void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) {
|
||||||
|
uint32_t suffix = 1;
|
||||||
|
|
||||||
|
snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr);
|
||||||
|
while (json.containsKey(key)) {
|
||||||
|
suffix++;
|
||||||
|
snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix); // add "0008/0001+2" suffix if duplicate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// First pass, parse all attributes in their native format
|
// First pass, parse all attributes in their native format
|
||||||
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
||||||
uint32_t i = offset;
|
uint32_t i = offset;
|
||||||
uint32_t len = _payload.len();
|
uint32_t len = _payload.len();
|
||||||
|
|
||||||
while (len - i >= 3) {
|
while (len >= i + 3) {
|
||||||
uint16_t attrid = _payload.get16(i);
|
uint16_t attrid = _payload.get16(i);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
char key[16];
|
char key[16];
|
||||||
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
|
generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
|
||||||
_cluster_id, attrid);
|
|
||||||
|
|
||||||
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
|
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
|
||||||
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
||||||
@ -445,8 +458,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||||||
|
|
||||||
if (0 == status) {
|
if (0 == status) {
|
||||||
char key[16];
|
char key[16];
|
||||||
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
|
generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
|
||||||
_cluster_id, attrid);
|
|
||||||
|
|
||||||
i += parseSingleAttribute(json, key, _payload, i, len);
|
i += parseSingleAttribute(json, key, _payload, i, len);
|
||||||
}
|
}
|
||||||
@ -472,7 +484,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
|||||||
// return value:
|
// return value:
|
||||||
// 0 = keep initial value
|
// 0 = keep initial value
|
||||||
// 1 = remove initial value
|
// 1 = remove initial value
|
||||||
typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name, uint16_t cluster, uint16_t attr);
|
typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
||||||
typedef struct Z_AttributeConverter {
|
typedef struct Z_AttributeConverter {
|
||||||
uint16_t cluster;
|
uint16_t cluster;
|
||||||
uint16_t attribute;
|
uint16_t attribute;
|
||||||
@ -775,13 +787,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Record Manuf
|
// Record Manuf
|
||||||
int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = value;
|
json[new_name] = value;
|
||||||
zigbee_devices.setManufId(shortaddr, value.as<const char*>());
|
zigbee_devices.setManufId(shortaddr, value.as<const char*>());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = value;
|
json[new_name] = value;
|
||||||
zigbee_devices.setModelId(shortaddr, value.as<const char*>());
|
zigbee_devices.setModelId(shortaddr, value.as<const char*>());
|
||||||
return 1;
|
return 1;
|
||||||
@ -789,29 +801,29 @@ int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j
|
|||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Remove attribute
|
// Remove attribute
|
||||||
int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
return 1; // remove original key
|
return 1; // remove original key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy value as-is
|
// Copy value as-is
|
||||||
int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = value;
|
json[new_name] = value;
|
||||||
return 1; // remove original key
|
return 1; // remove original key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add pressure unit
|
// Add pressure unit
|
||||||
int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = F(D_UNIT_PRESSURE);
|
json[new_name] = F(D_UNIT_PRESSURE);
|
||||||
return 0; // keep original key
|
return 0; // keep original key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert int to float and divide by 100
|
// Convert int to float and divide by 100
|
||||||
int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = ((float)value) / 100.0f;
|
json[new_name] = ((float)value) / 100.0f;
|
||||||
return 1; // remove original key
|
return 1; // remove original key
|
||||||
}
|
}
|
||||||
// Convert int to float and divide by 10
|
// Convert int to float and divide by 10
|
||||||
int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
json[new_name] = ((float)value) / 10.0f;
|
json[new_name] = ((float)value) / 10.0f;
|
||||||
return 1; // remove original key
|
return 1; // remove original key
|
||||||
}
|
}
|
||||||
@ -825,7 +837,7 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aqara Vibration Sensor - special proprietary attributes
|
// Aqara Vibration Sensor - special proprietary attributes
|
||||||
int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
//json[new_name] = value;
|
//json[new_name] = value;
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case 0x0055:
|
case 0x0055:
|
||||||
@ -879,7 +891,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje
|
|||||||
return 1; // remove original key
|
return 1; // remove original key
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
|
int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
||||||
String hex = value;
|
String hex = value;
|
||||||
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
@ -925,11 +937,19 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
|
|||||||
String key_string = kv.key;
|
String key_string = kv.key;
|
||||||
const char * key = key_string.c_str();
|
const char * key = key_string.c_str();
|
||||||
JsonVariant& value = kv.value;
|
JsonVariant& value = kv.value;
|
||||||
// Check that format looks like "CCCC/AAAA"
|
// Check that format looks like "CCCC/AAAA" or "CCCC/AAAA+d"
|
||||||
char * delimiter = strchr(key, '/');
|
char * delimiter = strchr(key, '/');
|
||||||
|
char * delimiter2 = strchr(key, '+');
|
||||||
if (delimiter) {
|
if (delimiter) {
|
||||||
|
uint16_t attribute;
|
||||||
|
uint16_t suffix = 1;
|
||||||
uint16_t cluster = strtoul(key, &delimiter, 16);
|
uint16_t cluster = strtoul(key, &delimiter, 16);
|
||||||
uint16_t attribute = strtoul(delimiter+1, nullptr, 16);
|
if (!delimiter2) {
|
||||||
|
attribute = strtoul(delimiter+1, nullptr, 16);
|
||||||
|
} else {
|
||||||
|
attribute = strtoul(delimiter+1, &delimiter2, 16);
|
||||||
|
suffix = strtoul(delimiter2+1, nullptr, 10);
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate on filter
|
// Iterate on filter
|
||||||
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
|
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
|
||||||
@ -939,7 +959,9 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
|
|||||||
|
|
||||||
if ((conv_cluster == cluster) &&
|
if ((conv_cluster == cluster) &&
|
||||||
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
|
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
|
||||||
int32_t drop = (*converter->func)(this, shortaddr, json, key, value, (const __FlashStringHelper*) converter->name, conv_cluster, conv_attribute);
|
String new_name_str = converter->name;
|
||||||
|
if (suffix > 1) { new_name_str += suffix; } // append suffix number
|
||||||
|
int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute);
|
||||||
if (drop) {
|
if (drop) {
|
||||||
json.remove(key);
|
json.remove(key);
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its
|
|||||||
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
|
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
|
||||||
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
|
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
|
||||||
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
|
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
|
||||||
|
const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address
|
||||||
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
|
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
|
||||||
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
||||||
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||||
|
@ -357,6 +357,28 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 45CA
|
||||||
|
int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
|
||||||
|
Z_ShortAddress srcAddr = buf.get16(2);
|
||||||
|
Z_IEEEAddress ieeeAddr = buf.get64(4);
|
||||||
|
Z_ShortAddress parentNw = buf.get16(12);
|
||||||
|
|
||||||
|
zigbee_devices.updateDevice(srcAddr, ieeeAddr);
|
||||||
|
|
||||||
|
char hex[20];
|
||||||
|
Uint64toHex(ieeeAddr, hex, 64);
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
||||||
|
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||||
|
",\"ParentNetwork\":\"0x%04X\"}}"),
|
||||||
|
ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw
|
||||||
|
);
|
||||||
|
|
||||||
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
||||||
|
XdrvRulesProcess();
|
||||||
|
//Z_SendActiveEpReq(srcAddr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
|
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
|
||||||
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
|
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
|
||||||
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
|
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
|
||||||
@ -378,16 +400,11 @@ void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, c
|
|||||||
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
||||||
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
||||||
if (json == nullptr) { return 0; } // don't crash if not found
|
if (json == nullptr) { return 0; } // don't crash if not found
|
||||||
|
|
||||||
// Post-provess for Aqara Presence Senson
|
// Post-provess for Aqara Presence Senson
|
||||||
Z_AqaraOccupancy(shortaddr, cluster, endpoint, json);
|
Z_AqaraOccupancy(shortaddr, cluster, endpoint, json);
|
||||||
|
|
||||||
String msg = "";
|
zigbee_devices.jsonPublish(shortaddr);
|
||||||
json->printTo(msg);
|
return 1;
|
||||||
zigbee_devices.jsonClear(shortaddr);
|
|
||||||
Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
|
||||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
||||||
XdrvRulesProcess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
||||||
@ -438,8 +455,13 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
|||||||
|
|
||||||
if (defer_attributes) {
|
if (defer_attributes) {
|
||||||
// Prepare for publish
|
// Prepare for publish
|
||||||
zigbee_devices.jsonAppend(srcaddr, json);
|
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
|
||||||
zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
|
// there is conflicting values, force a publish of the previous message now and don't coalesce
|
||||||
|
zigbee_devices.jsonPublish(srcaddr);
|
||||||
|
} else {
|
||||||
|
zigbee_devices.jsonAppend(srcaddr, json);
|
||||||
|
zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Publish immediately
|
// Publish immediately
|
||||||
msg = "";
|
msg = "";
|
||||||
@ -459,6 +481,7 @@ typedef struct Z_Dispatcher {
|
|||||||
// Filters for ZCL frames
|
// Filters for ZCL frames
|
||||||
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
||||||
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
|
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
|
||||||
|
ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
|
||||||
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
|
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
|
||||||
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
|
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
|
||||||
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
||||||
@ -466,6 +489,7 @@ ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
|||||||
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
||||||
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
||||||
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
|
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
|
||||||
|
{ AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd },
|
||||||
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
|
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
|
||||||
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
||||||
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
||||||
|
@ -31,12 +31,14 @@ TasmotaSerial *ZigbeeSerial = nullptr;
|
|||||||
const char kZigbeeCommands[] PROGMEM = "|"
|
const char kZigbeeCommands[] PROGMEM = "|"
|
||||||
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
||||||
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
||||||
D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ ;
|
D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE
|
||||||
|
;
|
||||||
|
|
||||||
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
||||||
&CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
&CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
||||||
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend,
|
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend,
|
||||||
&CmndZigbeeProbe, &CmndZigbeeRead };
|
&CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive
|
||||||
|
};
|
||||||
|
|
||||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||||
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
|
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
|
||||||
@ -268,7 +270,7 @@ void CmndZigbeeStatus(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CmndZigbeeZNPSend(void)
|
void CmndZigbeeZNPSendOrReceive(bool send)
|
||||||
{
|
{
|
||||||
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
|
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
|
||||||
uint8_t code;
|
uint8_t code;
|
||||||
@ -286,11 +288,26 @@ void CmndZigbeeZNPSend(void)
|
|||||||
size -= 2;
|
size -= 2;
|
||||||
codes += 2;
|
codes += 2;
|
||||||
}
|
}
|
||||||
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
if (send) {
|
||||||
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
||||||
|
} else {
|
||||||
|
ZigbeeProcessInput(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For debug purposes only, simulates a message received
|
||||||
|
void CmndZigbeeZNPReceive(void)
|
||||||
|
{
|
||||||
|
CmndZigbeeZNPSendOrReceive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndZigbeeZNPSend(void)
|
||||||
|
{
|
||||||
|
CmndZigbeeZNPSendOrReceive(true);
|
||||||
|
}
|
||||||
|
|
||||||
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
||||||
if ((len < 2) || (len > 252)) {
|
if ((len < 2) || (len > 252)) {
|
||||||
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
|
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user