Merge pull request #7226 from s-hadinger/zigbee_read

Add Zigbee send automatic ZigbeeRead after sending a command
This commit is contained in:
Theo Arends 2019-12-15 18:28:56 +01:00 committed by GitHub
commit cea842dab0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 21 deletions

View File

@ -3,6 +3,7 @@
### 7.1.2.6 20191214
- Change some more Settings locations freeing up space for future single char allowing variable length text
- Add Zigbee send automatic ZigbeeRead after sending a command
### 7.1.2.5 20191213

View File

@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
#define ZIGBEE_VERBOSE // output versbose MQTT Zigbee logs. Will remain active for now
typedef uint64_t Z_IEEEAddress;
typedef uint16_t Z_ShortAddress;

View File

@ -0,0 +1,26 @@
/*
xdrv_23_zigbee_1_headers.ino - zigbee support for Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
// contains some definitions for functions used before their declarations
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1);
#endif // USE_ZIGBEE

View File

@ -22,6 +22,9 @@
#include <vector>
#include <map>
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
typedef struct Z_Device {
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint64_t longaddr; // 0x00 means unspecified
@ -33,6 +36,12 @@ typedef struct Z_Device {
std::vector<uint32_t> endpoints; // encoded as high 16 bits is endpoint, low 16 bits is ProfileId
std::vector<uint32_t> clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
std::vector<uint32_t> clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
// below are per device timers, used for example to query the new state of the device
uint32_t timer; // millis() when to fire the timer, 0 if no timer
uint16_t cluster; // cluster to use for the timer
uint16_t endpoint; // endpoint to use for timer
uint32_t value; // any raw value to use for the timer
Z_DeviceTimer func; // function to call when timer occurs
} Z_Device;
// All devices are stored in a Vector
@ -70,6 +79,11 @@ public:
// Dump json
String dump(uint32_t dump_mode, int32_t device_num = 0) const;
// Timers
void resetTimer(uint32_t shortaddr);
void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func);
void runTimer(void);
private:
std::vector<Z_Device> _devices = {};
@ -157,7 +171,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
String(), // FriendlyName
std::vector<uint32_t>(),
std::vector<uint32_t>(),
std::vector<uint32_t>() };
std::vector<uint32_t>(),
0,0,0,0,
nullptr };
_devices.push_back(device);
return _devices.back();
}
@ -346,6 +362,47 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) {
_updateLastSeen(device);
}
// Per device timers
//
// Reset the timer for a specific device
void Z_Devices::resetTimer(uint32_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
device.timer = 0;
device.func = nullptr;
}
// Set timer for a specific device
void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
device.cluster = cluster;
device.endpoint = endpoint;
device.value = value;
device.func = func;
device.timer = wait_ms + millis();
}
// Run timer at each tick
void Z_Devices::runTimer(void) {
uint32_t now = millis();
for (std::vector<Z_Device>::iterator it = _devices.begin(); it != _devices.end(); ++it) {
Z_Device &device = *it;
uint16_t shortaddr = device.shortaddr;
uint32_t timer = device.timer;
if ((timer) && (timer <= now)) {
// trigger the timer
(*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value);
device.timer = 0; // cancel the timer
}
}
}
// Dump the internal memory of Zigbee devices
// Mode = 1: simple dump of devices addresses and names
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters

View File

@ -53,7 +53,6 @@ public:
uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
uint32_t timestamp) {
#ifdef ZIGBEE_VERBOSE
char hex_char[_payload.len()*2+2];
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
@ -69,7 +68,6 @@ public:
timestamp,
_frame_control, _manuf_code, _transact_seq, _cmd_id,
hex_char);
#endif
}
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
@ -497,7 +495,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0007, 0x0000, "SwitchType", &Z_Copy },
// Level Control cluster
{ 0x0008, 0x0000, "CurrentLevel", &Z_Copy },
{ 0x0008, 0x0000, "Dimmer", &Z_Copy },
// { 0x0008, 0x0001, "RemainingTime", &Z_Copy },
// { 0x0008, 0x0010, "OnOffTransitionTime", &Z_Copy },
// { 0x0008, 0x0011, "OnLevel", &Z_Copy },
@ -652,14 +650,14 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
// Color Control cluster
{ 0x0300, 0x0000, "CurrentHue", &Z_Copy },
{ 0x0300, 0x0001, "CurrentSaturation", &Z_Copy },
{ 0x0300, 0x0000, "Hue", &Z_Copy },
{ 0x0300, 0x0001, "Sat", &Z_Copy },
{ 0x0300, 0x0002, "RemainingTime", &Z_Copy },
{ 0x0300, 0x0003, "CurrentX", &Z_Copy },
{ 0x0300, 0x0004, "CurrentY", &Z_Copy },
{ 0x0300, 0x0003, "X", &Z_Copy },
{ 0x0300, 0x0004, "Y", &Z_Copy },
{ 0x0300, 0x0005, "DriftCompensation", &Z_Copy },
{ 0x0300, 0x0006, "CompensationText", &Z_Copy },
{ 0x0300, 0x0007, "ColorTemperatureMireds",&Z_Copy },
{ 0x0300, 0x0007, "CT", &Z_Copy },
{ 0x0300, 0x0008, "ColorMode", &Z_Copy },
{ 0x0300, 0x0010, "NumberOfPrimaries", &Z_Copy },
{ 0x0300, 0x0011, "Primary1X", &Z_Copy },

View File

@ -47,6 +47,64 @@ const Z_CommandConverter Z_Commands[] = {
{ "ShutterTilt", "0102!08xx"}, // Tilt percentage
};
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
// Below are the attributes we wand to read from each cluster
const uint8_t CLUSTER_0006[] = { ZLE(0x0000) }; // Power
const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
size_t attrs_len = 0;
const uint8_t* attrs = nullptr;
switch (cluster) {
case 0x0006: // for On/Off
attrs = CLUSTER_0006;
attrs_len = sizeof(CLUSTER_0006);
break;
case 0x0008: // for Dimmer
attrs = CLUSTER_0008;
attrs_len = sizeof(CLUSTER_0008);
break;
case 0x0009: // for Alarms
attrs = CLUSTER_0009;
attrs_len = sizeof(CLUSTER_0009);
break;
case 0x0300: // for Lights
attrs = CLUSTER_0300;
attrs_len = sizeof(CLUSTER_0300);
break;
}
if (attrs) {
ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
}
}
// set a timer to read back the value in the future
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006: // for On/Off
case 0x0009: // for Alamrs
wait_ms = 200; // wait 0.2 s
break;
case 0x0008: // for Dimmer
case 0x0300: // for Color
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102: // for Shutters
wait_ms = 10000; // wait 10.0 s
break;
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, wait_ms, cluster, endpoint, 0 /* value */, &Z_ReadAttrCallback);
}
}
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
char parm_uc[16]; // used to convert JSON keys to uppercase
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
@ -114,6 +172,7 @@ const uint8_t kZ_Numbers[] PROGMEM = { 0,0,0,0,0,
2,2,
255 };
// Convert an alias like "On" to the corresponding number
uint32_t ZigbeeAliasOrNumber(const char *state_text) {
char command[16];
int state_number = GetCommandCode(command, sizeof(command), state_text, kZ_Alias);

View File

@ -372,12 +372,10 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
zigbee_devices.updateLastSeen(srcaddr);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
#ifdef ZIGBEE_VERBOSE
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
#endif
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);

View File

@ -191,14 +191,9 @@ void ZigbeeInput(void)
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
#ifdef ZIGBEE_VERBOSE
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPRECEIVED " %s"),
hex_char);
// Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
// MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
// XdrvRulesProcess();
#endif
// now process the message
ZigbeeProcessInput(znp_buffer);
@ -320,15 +315,13 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
#ifdef ZIGBEE_VERBOSE
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
#endif
}
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1) {
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId) {
SBuffer buf(25+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST); // 01
@ -423,6 +416,10 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
// everything is good, we can send the command
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
// now set the timer, if any, to read back the state later
if (clusterSpecific) {
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
}
ResponseCmndDone();
}
@ -648,6 +645,11 @@ bool Xdrv23(uint8_t function)
if (zigbee.active) {
switch (function) {
case FUNC_EVERY_50_MSECOND:
if (!zigbee.init_phase) {
zigbee_devices.runTimer();
}
break;
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (zigbee.state_machine) {