diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index e45d2efce..828b5d515 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -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 diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 996d877f8..0c74c9d8a 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -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; diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino new file mode 100644 index 000000000..b017d8e2e --- /dev/null +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -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 . +*/ + +#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 diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index f76c882c7..de13990c2 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -22,6 +22,9 @@ #include #include + +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 endpoints; // encoded as high 16 bits is endpoint, low 16 bits is ProfileId std::vector clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number std::vector 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 _devices = {}; @@ -157,7 +171,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { String(), // FriendlyName std::vector(), std::vector(), - std::vector() }; + std::vector(), + 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::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 diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 4defbf043..a56ada927 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -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 }, diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index c877bd3a3..70a8f0788 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -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); diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index acd7e5d26..f88bf15e2 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -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); diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 3bfd6c4e3..461b3af32 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -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) {