From ac4d4ac57114700e25cd54f103f777b2c6ca19b8 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Mon, 23 Mar 2020 22:46:26 +0100 Subject: [PATCH] Code optimization, cleaning and more error codes --- tasmota/support_static_buffer.ino | 4 +- tasmota/xdrv_23_zigbee_0_constants.ino | 106 +++------- tasmota/xdrv_23_zigbee_2_devices.ino | 93 ++++----- tasmota/xdrv_23_zigbee_6_commands.ino | 7 + tasmota/xdrv_23_zigbee_7_statemachine.ino | 88 ++++++++- tasmota/xdrv_23_zigbee_8_parsers.ino | 226 ++++++++++++--------- tasmota/xdrv_23_zigbee_9_impl.ino | 227 +++++++++++----------- 7 files changed, 406 insertions(+), 345 deletions(-) diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino index bec831cc9..ea21b2805 100644 --- a/tasmota/support_static_buffer.ino +++ b/tasmota/support_static_buffer.ino @@ -112,7 +112,7 @@ public: } size_t addBuffer(const uint8_t *buf2, size_t len2) { - if (len() + len2 <= size()) { + if ((buf2) && (len() + len2 <= size())) { for (uint32_t i = 0; i < len2; i++) { _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); } @@ -121,7 +121,7 @@ public: } size_t addBuffer(const char *buf2, size_t len2) { - if (len() + len2 <= size()) { + if ((buf2) && (len() + len2 <= size())) { for (uint32_t i = 0; i < len2; i++) { _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); } diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index d97a26f9f..1a23c98dd 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -393,88 +393,36 @@ typedef struct Z_StatusLine { const char * status_msg; } Z_StatusLine; -ZF(SUCCESS) -ZF(FAILURE) -ZF(NOT_AUTHORIZED) -ZF(RESERVED_FIELD_NOT_ZERO) -ZF(MALFORMED_COMMAND) -ZF(UNSUP_CLUSTER_COMMAND) -ZF(UNSUP_GENERAL_COMMAND) -ZF(UNSUP_MANUF_CLUSTER_COMMAND) -ZF(UNSUP_MANUF_GENERAL_COMMAND) -ZF(INVALID_FIELD) -ZF(UNSUPPORTED_ATTRIBUTE) -ZF(INVALID_VALUE) -ZF(READ_ONLY) -ZF(INSUFFICIENT_SPACE) -ZF(DUPLICATE_EXISTS) -ZF(NOT_FOUND) -ZF(UNREPORTABLE_ATTRIBUTE) -ZF(INVALID_DATA_TYPE) -ZF(INVALID_SELECTOR) -ZF(WRITE_ONLY) -ZF(INCONSISTENT_STARTUP_STATE) -ZF(DEFINED_OUT_OF_BAND) -ZF(INCONSISTENT) -ZF(ACTION_DENIED) -ZF(TIMEOUT) -ZF(ABORT) -ZF(INVALID_IMAGE) -ZF(WAIT_FOR_DATA) -ZF(NO_IMAGE_AVAILABLE) -ZF(REQUIRE_MORE_IMAGE) -ZF(NOTIFICATION_PENDING) -ZF(HARDWARE_FAILURE) -ZF(SOFTWARE_FAILURE) -ZF(CALIBRATION_ERROR) -ZF(UNSUPPORTED_CLUSTER) +// Undocumented Zigbee ZCL code here: https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/Zigbee-Error-Codes-in-the-Log +String getZigbeeStatusMessage(uint8_t status) { + static const char StatusMsg[] PROGMEM = "SUCCESS|FAILURE|NOT_AUTHORIZED|RESERVED_FIELD_NOT_ZERO|MALFORMED_COMMAND|UNSUP_CLUSTER_COMMAND|UNSUP_GENERAL_COMMAND" + "|UNSUP_MANUF_CLUSTER_COMMAND|UNSUP_MANUF_GENERAL_COMMAND|INVALID_FIELD|UNSUPPORTED_ATTRIBUTE|INVALID_VALE|READ_ONLY" + "|INSUFFICIENT_SPACE|DUPLICATE_EXISTS|NOT_FOUND|UNREPORTABLE_ATTRIBUTE|INVALID_DATA_TYPE|INVALID_SELECTOR|WRITE_ONLY" + "|INCONSISTENT_STARTUP_STATE|DEFINED_OUT_OF_BAND|INCONSISTENT|ACTION_DENIED|TIMEOUT|ABORT|INVALID_IMAGE|WAIT_FOR_DATA" + "|NO_IMAGE_AVAILABLE|REQUIRE_MORE_IMAGE|NOTIFICATION_PENDING|HARDWARE_FAILURE|SOFTWARE_FAILURE|CALIBRATION_ERROR|UNSUPPORTED_CLUSTER" + "|CHANNEL_ACCESS_FAILURE|NO_ACK|NO_APP_ACK|NO_ROUTE" + ; + static const uint8_t StatusIdx[] PROGMEM = { 0x00, 0x01, 0x7E, 0x7F, 0x80, 0x81, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0xC0, 0xC1, 0xC2, 0xC3, + 0xE1, 0xE9, 0xA7, 0xD0}; -const Z_StatusLine Z_Status[] PROGMEM = { - 0x00, Z(SUCCESS), - 0x01, Z(FAILURE), - 0x7E, Z(NOT_AUTHORIZED), - 0x7F, Z(RESERVED_FIELD_NOT_ZERO), - 0x80, Z(MALFORMED_COMMAND), - 0x81, Z(UNSUP_CLUSTER_COMMAND), - 0x82, Z(UNSUP_GENERAL_COMMAND), - 0x83, Z(UNSUP_MANUF_CLUSTER_COMMAND), - 0x84, Z(UNSUP_MANUF_GENERAL_COMMAND), - 0x85, Z(INVALID_FIELD), - 0x86, Z(UNSUPPORTED_ATTRIBUTE), - 0x87, Z(INVALID_VALUE), - 0x88, Z(READ_ONLY), - 0x89, Z(INSUFFICIENT_SPACE), - 0x8A, Z(DUPLICATE_EXISTS), - 0x8B, Z(NOT_FOUND), - 0x8C, Z(UNREPORTABLE_ATTRIBUTE), - 0x8D, Z(INVALID_DATA_TYPE), - 0x8E, Z(INVALID_SELECTOR), - 0x8F, Z(WRITE_ONLY), - 0x90, Z(INCONSISTENT_STARTUP_STATE), - 0x91, Z(DEFINED_OUT_OF_BAND), - 0x92, Z(INCONSISTENT), - 0x93, Z(ACTION_DENIED), - 0x94, Z(TIMEOUT), - 0x95, Z(ABORT), - 0x96, Z(INVALID_IMAGE), - 0x97, Z(WAIT_FOR_DATA), - 0x98, Z(NO_IMAGE_AVAILABLE), - 0x99, Z(REQUIRE_MORE_IMAGE), - 0x9A, Z(NOTIFICATION_PENDING), - 0xC0, Z(HARDWARE_FAILURE), - 0xC1, Z(SOFTWARE_FAILURE), - 0xC2, Z(CALIBRATION_ERROR), - 0xC3, Z(UNSUPPORTED_CLUSTER), -}; - -const __FlashStringHelper* getZigbeeStatusMessage(uint8_t status) { - for (uint32_t i = 0; i < sizeof(Z_Status) / sizeof(Z_Status[0]); i++) { - const Z_StatusLine *statl = &Z_Status[i]; - if (statl->status == status) { - return (const __FlashStringHelper*) statl->status_msg; + char msg[32]; + int32_t idx = -1; + for (uint32_t i = 0; i < sizeof(StatusIdx); i++) { + if (status == pgm_read_byte(&StatusIdx[i])) { + idx = i; + break; } } - return F(""); + if (idx >= 0) { + GetTextIndexed(msg, sizeof(msg), idx, StatusMsg); + } else { + *msg = 0x00; // empty string + } + return String(msg); } #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 7b02e33ff..e4c6f6df0 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -26,7 +26,9 @@ #endif const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds -typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value); +/*********************************************************************************************\ + * Structures for device configuration +\*********************************************************************************************/ const size_t endpoints_max = 8; // we limit to 8 endpoints @@ -53,6 +55,12 @@ typedef struct Z_Device { uint16_t x, y; // last color [x,y] } Z_Device; +/*********************************************************************************************\ + * Structures for deferred callbacks +\*********************************************************************************************/ + +typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value); + // Category for Deferred actions, this allows to selectively remove active deferred or update them typedef enum Z_Def_Category { Z_CAT_NONE = 0, // no category, it will happen anyways @@ -76,6 +84,10 @@ typedef struct Z_Deferred { Z_DeviceTimer func; // function to call when timer occurs } Z_Deferred; +/*********************************************************************************************\ + * Singleton for device configuration +\*********************************************************************************************/ + // All devices are stored in a Vector // Invariants: // - shortaddr is unique if not null @@ -190,13 +202,22 @@ private: // Create a new entry in the devices list - must be called if it is sure it does not already exist Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0); void freeDeviceEntry(Z_Device *device); + + void setStringAttribute(char*& attr, const char * str); }; +/*********************************************************************************************\ + * Singleton variable +\*********************************************************************************************/ Z_Devices zigbee_devices = Z_Devices(); // Local coordinator information uint64_t localIEEEAddr = 0; +/*********************************************************************************************\ + * Implementation +\*********************************************************************************************/ + // https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/ template < typename T> bool Z_Devices::findInVector(const std::vector & vecOfElements, const T & element) { @@ -493,73 +514,55 @@ uint8_t Z_Devices::findFirstEndpoint(uint16_t shortaddr) const { return device.endpoints[0]; // returns 0x00 if no endpoint } -void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found +void Z_Devices::setStringAttribute(char*& attr, const char * str) { size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string - if ((!device.manufacturerId) && (0 == str_len)) { return; } // if both empty, don't do anything - if (device.manufacturerId) { + if ((nullptr == attr) && (0 == str_len)) { return; } // if both empty, don't do anything + if (attr) { // we already have a value - if (strcmp(device.manufacturerId, str) != 0) { + if (strcmp(attr, str) != 0) { // new value - free(device.manufacturerId); // free previous value - device.manufacturerId = nullptr; + free(attr); // free previous value + attr = nullptr; } else { return; // same value, don't change anything } } if (str_len) { - device.manufacturerId = (char*) malloc(str_len + 1); - strlcpy(device.manufacturerId, str, str_len + 1); + attr = (char*) malloc(str_len + 1); + strlcpy(attr, str, str_len + 1); } dirty(); } +// +// Sets the ManufId for a device. +// No action taken if the device does not exist. +// Inputs: +// - shortaddr: 16-bits short address of the device. No action taken if the device is unknown +// - str: string pointer, if nullptr it is considered as empty string +// Impact: +// - Any actual change in ManufId (i.e. setting a different value) triggers a `dirty()` and saving to Flash +// +void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + + setStringAttribute(device.manufacturerId, str); +} + void Z_Devices::setModelId(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found - size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string - if ((!device.modelId) && (0 == str_len)) { return; } // if both empty, don't do anything - if (device.modelId) { - // we already have a value - if (strcmp(device.modelId, str) != 0) { - // new value - free(device.modelId); // free previous value - device.modelId = nullptr; - } else { - return; // same value, don't change anything - } - } - if (str_len) { - device.modelId = (char*) malloc(str_len + 1); - strlcpy(device.modelId, str, str_len + 1); - } - dirty(); + setStringAttribute(device.modelId, str); } void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found - size_t str_len = str ? strlen(str) : 0; // len, handle both null ptr and zero length string - if ((!device.friendlyName) && (0 == str_len)) { return; } // if both empty, don't do anything - if (device.friendlyName) { - // we already have a value - if (strcmp(device.friendlyName, str) != 0) { - // new value - free(device.friendlyName); // free previous value - device.friendlyName = nullptr; - } else { - return; // same value, don't change anything - } - } - if (str_len) { - device.friendlyName = (char*) malloc(str_len + 1); - strlcpy(device.friendlyName, str, str_len + 1); - } - dirty(); + setStringAttribute(device.friendlyName, str); } const char * Z_Devices::getFriendlyName(uint16_t shortaddr) const { diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 8d756c95d..4788ddf67 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -19,6 +19,10 @@ #ifdef USE_ZIGBEE +/*********************************************************************************************\ + * ZCL Command Structures +\*********************************************************************************************/ + typedef struct Z_CommandConverter { const char * tasmota_cmd; uint16_t cluster; @@ -130,6 +134,9 @@ const Z_CommandConverter Z_Commands[] PROGMEM = { { Z(GetSceneMembership),0x0005, 0x06, 0x82,Z(xxyyzzzz) }, // specific }; +/*********************************************************************************************\ + * ZCL Read Light status based on cluster number +\*********************************************************************************************/ #define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian // Below are the attributes we wand to read from each cluster diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 2073fb9eb..1c4f782b4 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -20,7 +20,7 @@ #ifdef USE_ZIGBEE // Status code used for ZigbeeStatus MQTT message -// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}} +// Ex: {"ZbStatus":{"Status": 3,"Message":"Configured, starting coordinator"}} const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration @@ -33,7 +33,6 @@ 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_SIMPLE_DESC = 33; // Simple Descriptor (clusters) const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address -//const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address 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_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version @@ -50,9 +49,6 @@ typedef union Zigbee_Instruction { } i; const void *p; // pointer } Zigbee_Instruction; -// -// Zigbee_Instruction z1 = { .i = {1,2,3}}; -// Zigbee_Instruction z3 = { .p = nullptr }; typedef struct Zigbee_Instruction_Type { uint8_t instr; @@ -488,7 +484,6 @@ void ZigbeeStateMachine_Run(void) { if (zigbee.state_waiting) { // state machine is waiting for external event or timeout // checking if timeout expired if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever - //AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "timeout occured pc=%d"), zigbee.pc); if (!zigbee.state_no_timeout) { AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "timeout, goto label %d"), zigbee.on_timeout_goto); ZigbeeGotoLabel(zigbee.on_timeout_goto); @@ -505,7 +500,6 @@ void ZigbeeStateMachine_Run(void) { zigbee.recv_until = false; zigbee.state_no_timeout = false; // reset the no_timeout for next instruction -// AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeStateMachine_Run PC = %d, Mem1 = %d"), zigbee.pc, ESP.getFreeHeap()); if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) { AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Invalid pc: %d, aborting"), zigbee.pc); zigbee.pc = -1; @@ -516,7 +510,6 @@ void ZigbeeStateMachine_Run(void) { } // load current instruction details - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Executing instruction pc=%d"), zigbee.pc); const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc]; cur_instr = pgm_read_byte(&cur_instr_line->i.i); cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); @@ -553,7 +546,6 @@ void ZigbeeStateMachine_Run(void) { case ZGB_INSTR_WAIT_FOREVER: zigbee.next_timeout = 0; zigbee.state_waiting = true; - //zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done break; case ZGB_INSTR_STOP: zigbee.state_machine = false; @@ -617,4 +609,82 @@ void ZigbeeStateMachine_Run(void) { } } +// +// Process a bytes buffer and call any callback that matches the received message +// +int32_t ZigbeeProcessInput(class SBuffer &buf) { + if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message + + // apply the receive filter, acts as 'startsWith()' + bool recv_filter_match = true; + bool recv_prefix_match = false; // do the first 2 bytes match the response + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (zigbee.recv_filter_len >= 2) { + recv_prefix_match = false; + if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) && + (pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) { + recv_prefix_match = true; + } + } + + for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) { + if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) { + recv_filter_match = false; + break; + } + } + } + + // if there is a recv_callback, call it now + int32_t res = -1; // default to ok + // res = 0 - proceed to next state + // res > 0 - proceed to the specified state + // res = -1 - silently ignore the message + // res <= -2 - move to error state + // pre-compute the suggested value + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (!recv_prefix_match) { + res = -1; // ignore + } else { // recv_prefix_match + if (recv_filter_match) { + res = 0; // ok + } else { + if (zigbee.recv_until) { + res = -1; // ignore until full match + } else { + res = -2; // error, because message is expected but wrong value + } + } + } + } else { // we don't have any filter, ignore message by default + res = -1; + } + + if (recv_prefix_match) { + if (zigbee.recv_func) { + res = (*zigbee.recv_func)(res, buf); + } + } + if (-1 == res) { + // if frame was ignored up to now + if (zigbee.recv_unexpected) { + res = (*zigbee.recv_unexpected)(res, buf); + } + } + + // change state accordingly + if (0 == res) { + // if ok, continue execution + zigbee.state_waiting = false; + } else if (res > 0) { + ZigbeeGotoLabel(res); // if >0 then go to specified label + } else if (-1 == res) { + // -1 means ignore message + // just do nothing + } else { + // any other negative value means error + ZigbeeGotoLabel(zigbee.on_error_goto); + } +} + #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index e9c4b6368..b10c4be05 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -19,6 +19,13 @@ #ifdef USE_ZIGBEE +/*********************************************************************************************\ + * Parsers for incoming ZNP messages +\*********************************************************************************************/ + +// +// Handle a "Receive Device Info" incoming message +// int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { // Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991 // IEEE Adr (8 bytes) = 0x00124B001D156362 @@ -76,12 +83,12 @@ int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) { } } -const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog"; - int32_t Z_Reboot(int32_t res, class SBuffer &buf) { // print information about the reboot of device // 4180.02.02.00.02.06.03 // + static const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog"; + uint8_t reason = buf.get8(2); uint8_t transport_rev = buf.get8(3); uint8_t product_id = buf.get8(4); @@ -140,6 +147,9 @@ int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) { } } +// +// Helper function, checks if the incoming buffer matches the 2-bytes prefix, i.e. message type in PMEM +// bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) { if ( (pgm_read_byte(&match[0]) == buf.get8(0)) && (pgm_read_byte(&match[1]) == buf.get8(1)) ) { @@ -149,6 +159,9 @@ bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) { } } +// +// Handle Permit Join response +// int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { // we received a PermitJoin status change uint8_t duration = buf.get8(2); @@ -176,22 +189,6 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { return -1; } -// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address -void Z_SendIEEEAddrReq(uint16_t shortaddr) { - uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ, - Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 }; - - ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq)); -} - -// Send ACTIVE_EP_REQ to collect active endpoints for this address -void Z_SendActiveEpReq(uint16_t shortaddr) { - uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, - Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; - - ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq)); -} - const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" }; int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { // Received ZDO_NODE_DESC_RSP @@ -226,6 +223,9 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { return -1; } +// +// Porcess Receive Active Endpoint +// int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) { // Received ZDO_ACTIVE_EP_RSP Z_ShortAddress srcAddr = buf.get16(2); @@ -254,37 +254,14 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) { return -1; } -void Z_SendAFInfoRequest(uint16_t shortaddr) { - uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr); - if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01 - uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); - - SBuffer buf(100); - buf.add8(Z_SREQ | Z_AF); // 24 - buf.add8(AF_DATA_REQUEST); // 01 - buf.add16(shortaddr); - buf.add8(endpoint); // dest endpoint - buf.add8(0x01); // source endpoint - buf.add16(0x0000); - buf.add8(transacid); - buf.add8(0x30); // 30 options - buf.add8(0x1E); // 1E radius - - buf.add8(3 + 2*sizeof(uint16_t)); // Len = 0x07 - buf.add8(0x00); // Frame Control Field - buf.add8(transacid); // Transaction Sequence Number - buf.add8(ZCL_READ_ATTRIBUTES); // 00 Command - buf.add16(0x0004); // 0400 ManufacturerName - buf.add16(0x0005); // 0500 ModelIdentifier - - ZigbeeZNPSend(buf.getBuffer(), buf.len()); -} - +// +// Handle IEEEAddr incoming message +// int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) { uint8_t status = buf.get8(2); Z_IEEEAddress ieeeAddr = buf.get64(3); Z_ShortAddress nwkAddr = buf.get16(11); - // uint8_t startIndex = buf.get8(13); + // uint8_t startIndex = buf.get8(13); // not used // uint8_t numAssocDev = buf.get8(14); if (0 == status) { // SUCCESS @@ -308,34 +285,6 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) { } return -1; } - -int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) { - Z_ShortAddress nwkAddr = buf.get16(2); - uint8_t status = buf.get8(4); - char status_message[32]; - - strncpy_P(status_message, (const char*) getZigbeeStatusMessage(status), sizeof(status_message)); - status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist - - const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); - if (friendlyName) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, friendlyName, status, status_message); - } else { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, status, status_message); - } - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); - XdrvRulesProcess(); - - return -1; -} - // // Report any AF_DATA_CONFIRM message // Ex: {"ZbConfirm":{"Endpoint":1,"Status":0,"StatusMessage":"SUCCESS"}} @@ -343,17 +292,13 @@ int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) { int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) { uint8_t status = buf.get8(2); uint8_t endpoint = buf.get8(3); - //uint8_t transId = buf.get8(4); - char status_message[32]; + //uint8_t transId = buf.get8(4); // unused if (status) { // only report errors - strncpy_P(status_message, (const char*) getZigbeeStatusMessage(status), sizeof(status_message)); - status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist - Response_P(PSTR("{\"" D_JSON_ZIGBEE_CONFIRM "\":{\"" D_CMND_ZIGBEE_ENDPOINT "\":%d" ",\"" D_JSON_ZIGBEE_STATUS "\":%d" ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), endpoint, status, status_message); + "}}"), endpoint, status, getZigbeeStatusMessage(status).c_str()); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); } @@ -361,6 +306,10 @@ int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) { return -1; } +// +// Handle Receive End Device Announce incoming message +// Send back Active Ep Req message +// int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { Z_ShortAddress srcAddr = buf.get16(2); Z_ShortAddress nwkAddr = buf.get16(4); @@ -386,7 +335,10 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { return -1; } +// +// Handle Receive TC Dev Ind incoming message // 45CA +// int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { Z_ShortAddress srcAddr = buf.get16(2); Z_IEEEAddress ieeeAddr = buf.get64(4); @@ -404,15 +356,82 @@ int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); - //Z_SendActiveEpReq(srcAddr); return -1; } +// +// Handle Bind Rsp incoming message +// +int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) { + Z_ShortAddress nwkAddr = buf.get16(2); + uint8_t status = buf.get8(4); + + const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); + if (friendlyName) { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" + ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"" + ",\"" D_JSON_ZIGBEE_STATUS "\":%d" + ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" + "}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str()); + } else { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" + ",\"" D_JSON_ZIGBEE_STATUS "\":%d" + ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" + "}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str()); + } + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + + return -1; +} + +/*********************************************************************************************\ + * Send specific ZNP messages +\*********************************************************************************************/ + +// +// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address +// +void Z_SendIEEEAddrReq(uint16_t shortaddr) { + uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ, Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 }; + + ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq)); +} + +// +// Send ACTIVE_EP_REQ to collect active endpoints for this address +// +void Z_SendActiveEpReq(uint16_t shortaddr) { + uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; + + ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq)); +} + +// +// Send AF Info Request +// +void Z_SendAFInfoRequest(uint16_t shortaddr) { + uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr); + if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01 + uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); + + uint8_t AFInfoReq[] = { Z_SREQ | Z_AF, AF_DATA_REQUEST, Z_B0(shortaddr), Z_B1(shortaddr), endpoint, + 0x01, 0x00, 0x00, transacid, 0x30, 0x1E, 3 + 2*sizeof(uint16_t), + 0x00, transacid, ZCL_READ_ATTRIBUTES, 0x04, 0x00, 0x05, 0x00 + }; + ZigbeeZNPSend(AFInfoReq, sizeof(AFInfoReq)); +} + + +/*********************************************************************************************\ + * Callbacks +\*********************************************************************************************/ + + // 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 -const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s - void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject *json) { + static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s // Read OCCUPANCY value if any const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY)); if (nullptr != &val_endpoint) { @@ -436,6 +455,10 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu return 1; } +/*********************************************************************************************\ + * Global dispatcher for incoming messages +\*********************************************************************************************/ + int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { uint16_t groupid = buf.get16(2); uint16_t clusterid = buf.get16(4); @@ -506,22 +529,24 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { return -1; } +// Structure for the Dispatcher callbacks table typedef struct Z_Dispatcher { const uint8_t* match; ZB_RecvMsgFunc func; } Z_Dispatcher; -// Filters for ZCL frames -ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480 -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_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_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_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581 -ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1 +// Ffilters based on ZNP frames +ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480 +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_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_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_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581 +ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1 +// Dispatcher callbacks table const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_AF_DATA_CONFIRM, &Z_DataConfirm }, { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, @@ -534,6 +559,10 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_ZDO_BIND_RSP, &Z_BindRsp }, }; +/*********************************************************************************************\ + * Default resolver +\*********************************************************************************************/ + int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { // Default message handler for new messages if (zigbee.init_phase) { @@ -549,12 +578,22 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { } } +/*********************************************************************************************\ + * Functions called by State Machine +\*********************************************************************************************/ + +// +// Callback for loading Zigbee configuration from Flash, called by the state machine +// int32_t Z_Load_Devices(uint8_t value) { // try to hidrate from known devices loadZigbeeDevices(); return 0; // continue } +// +// Send messages to query the state of each Hue emulated light +// int32_t Z_Query_Bulbs(uint8_t value) { // Scan all devices and send deferred requests to know the state of bulbs uint32_t wait_ms = 1000; // start with 1.0 s delay @@ -588,6 +627,9 @@ int32_t Z_Query_Bulbs(uint8_t value) { return 0; // continue } +// +// Zigbee initialization is complete, let the party begin +// int32_t Z_State_Ready(uint8_t value) { zigbee.init_phase = false; // initialization phase complete return 0; // continue diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 12c9ff98f..49f89c9c7 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -47,85 +47,10 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbLight, CmndZbRestore, }; -int32_t ZigbeeProcessInput(class SBuffer &buf) { - if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message - - // apply the receive filter, acts as 'startsWith()' - bool recv_filter_match = true; - bool recv_prefix_match = false; // do the first 2 bytes match the response - if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { - if (zigbee.recv_filter_len >= 2) { - recv_prefix_match = false; - if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) && - (pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) { - recv_prefix_match = true; - } - } - - for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) { - if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) { - recv_filter_match = false; - break; - } - } - - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match); - } - - // if there is a recv_callback, call it now - int32_t res = -1; // default to ok - // res = 0 - proceed to next state - // res > 0 - proceed to the specified state - // res = -1 - silently ignore the message - // res <= -2 - move to error state - // pre-compute the suggested value - if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { - if (!recv_prefix_match) { - res = -1; // ignore - } else { // recv_prefix_match - if (recv_filter_match) { - res = 0; // ok - } else { - if (zigbee.recv_until) { - res = -1; // ignore until full match - } else { - res = -2; // error, because message is expected but wrong value - } - } - } - } else { // we don't have any filter, ignore message by default - res = -1; - } - - if (recv_prefix_match) { - if (zigbee.recv_func) { - res = (*zigbee.recv_func)(res, buf); - } - } - if (-1 == res) { - // if frame was ignored up to now - if (zigbee.recv_unexpected) { - res = (*zigbee.recv_unexpected)(res, buf); - } - } - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: res = %d"), res); - - // change state accordingly - if (0 == res) { - // if ok, continue execution - zigbee.state_waiting = false; - } else if (res > 0) { - ZigbeeGotoLabel(res); // if >0 then go to specified label - } else if (-1 == res) { - // -1 means ignore message - // just do nothing - } else { - // any other negative value means error - ZigbeeGotoLabel(zigbee.on_error_goto); - } -} - -void ZigbeeInput(void) +// +// Called at event loop, checks for incoming data from the CC2530 +// +void ZigbeeInputLoop(void) { static uint32_t zigbee_polling_window = 0; static uint8_t fcs = ZIGBEE_SOF; @@ -217,6 +142,7 @@ void ZigbeeInput(void) /********************************************************************************************/ +// Initialize internal structures void ZigbeeInit(void) { // AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem1 = %d"), ESP.getFreeHeap()); @@ -260,10 +186,10 @@ uint32_t strToUInt(const JsonVariant &val) { return 0; // couldn't parse anything } +// Do a factory reset of the CC2530 const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = { Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x01 /* STARTOPT_CLEAR_CONFIG */}; //"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG -// Do a factory reset of the CC2530 void CmndZbReset(void) { if (ZigbeeSerial) { switch (XdrvMailbox.payload) { @@ -279,6 +205,10 @@ void CmndZbReset(void) { } } +// +// Same code for `ZbZNPSend` and `ZbZNPReceive` +// building the complete message (intro, length) +// void CmndZbZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { @@ -298,8 +228,10 @@ void CmndZbZNPSendOrReceive(bool send) codes += 2; } if (send) { + // Command was `ZbZNPSend` ZigbeeZNPSend(buf.getBuffer(), buf.len()); } else { + // Command was `ZbZNPReceive` ZigbeeProcessInput(buf); } } @@ -347,6 +279,21 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { ToHex_P(msg, len, hex_char, sizeof(hex_char))); } +// +// Internal function, send the low-level frame +// Input: +// - shortaddr: 16-bits short address, or 0x0000 if group address +// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr +// - clusterIf: 16-bits cluster number +// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address. +// - cmdId: 8-bits ZCL command number +// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL +// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr +// - len: length of the 'msg' payload +// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond +// - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number +// Returns: None +// void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) { SBuffer buf(32+len); @@ -379,7 +326,22 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI ZigbeeZNPSend(buf.getBuffer(), buf.len()); } -// Send a command specified as an HEX string for the workload +/********************************************************************************************/ +// +// High-level function +// Send a command specified as an HEX string for the workload. +// The target endpoint is computed if zero, i.e. sent to the first known endpoint of the device. +// If cluster-specific, a timer may be set calling `zigbeeSetCommandTimer()`, for ex to coalesce attributes or Aqara presence sensor +// +// Inputs: +// - shortaddr: 16-bits short address, or 0x0000 if group address +// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr +// - endpoint: 8-bits target endpoint (source is always 0x01), if 0x00, it will be guessed from ZbStatus information (basically the first endpoint of the device) +// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL +// - clusterIf: 16-bits cluster number +// - param: pointer to HEX string for payload, should not be nullptr +// Returns: None +// void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, bool clusterSpecific, uint16_t cluster, uint8_t cmd, const char *param) { size_t size = param ? strlen(param) : 0; @@ -395,15 +357,15 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, if ((0 == endpoint) && (shortaddr)) { // endpoint is not specified, let's try to find it from shortAddr, unless it's a group address endpoint = zigbee_devices.findFirstEndpoint(shortaddr); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), shortaddr, groupaddr, cluster, endpoint, cmd, param); - if ((0 == endpoint) && (shortaddr)) { + if ((0 == endpoint) && (shortaddr)) { // endpoint null is ok for group address AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); return; - } // endpoint null is ok for group address + } // everything is good, we can send the command ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len(), true, zigbee_devices.getNextSeqNumber(shortaddr)); @@ -413,21 +375,24 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, } } +// +// Command `ZbSend` +// void CmndZbSend(void) { - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} } - // ZigbeeSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} } - // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} } + // ZbSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} } if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } DynamicJsonBuffer jsonBuf; - JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data); if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params @@ -463,16 +428,16 @@ void CmndZbSend(void) { // If String, it's a low level command if (val_cmd.is()) { // we have a high-level command - JsonObject &cmd_obj = val_cmd.as(); + const JsonObject &cmd_obj = val_cmd.as(); int32_t cmd_size = cmd_obj.size(); if (cmd_size > 1) { Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size); return; } else if (1 == cmd_size) { // We have exactly 1 command, parse it - JsonObject::iterator it = cmd_obj.begin(); // just get the first key/value + JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value String key = it->key; - JsonVariant& value = it->value; + const JsonVariant& value = it->value; uint32_t x = 0, y = 0, z = 0; uint16_t cmd_var; @@ -526,7 +491,7 @@ void CmndZbSend(void) { } else { // we have zero command, pass through until last error for missing command } - } else if (val_cmd.is()) { + } else if (val_cmd.is()) { // low-level command cmd_str = val_cmd.as(); // Now parse the string to extract cluster, command, and payload @@ -564,16 +529,18 @@ void CmndZbSend(void) { Response_P(PSTR("Missing zigbee 'Send'")); return; } - } +// +// Command `ZbBind` +// void CmndZbBind(void) { - // ZbBind { "device":"0x1234", "endpoint":1, "cluster":6 } + // ZbBind {"Device":"", "Endpoint":, "Cluster":, "ToDevice":"", "ToEndpoint":, "ToGroup": } // local endpoint is always 1, IEEE addresses are calculated if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } DynamicJsonBuffer jsonBuf; - JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data); if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params @@ -658,6 +625,9 @@ void CmndZbProbe(void) { CmndZbProbeOrPing(true); } +// +// Common code for `ZbProbe` and `ZbPing` +// void CmndZbProbeOrPing(boolean probe) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); @@ -677,12 +647,15 @@ void CmndZbPing(void) { CmndZbProbeOrPing(false); } +// +// Command `ZbName` // Specify, read or erase a Friendly Name +// void CmndZbName(void) { // Syntax is: - // ZigbeeName , - assign a friendly name - // ZigbeeName - display the current friendly name - // ZigbeeName , - remove friendly name + // ZbName , - assign a friendly name + // ZbName - display the current friendly name + // ZbName , - remove friendly name // // Where can be: short_addr, long_addr, device_index, friendly_name @@ -706,12 +679,15 @@ void CmndZbName(void) { } } +// +// Command `ZbName` // Specify, read or erase a ModelId, only for debug purposes +// void CmndZbModelId(void) { // Syntax is: - // ZigbeeName , - assign a friendly name - // ZigbeeName - display the current friendly name - // ZigbeeName , - remove friendly name + // ZbName , - assign a friendly name + // ZbName - display the current friendly name + // ZbName , - remove friendly name // // Where can be: short_addr, long_addr, device_index, friendly_name @@ -735,6 +711,8 @@ void CmndZbModelId(void) { } } +// +// Command `ZbLight` // Specify, read or erase a Light type for Hue/Alexa integration void CmndZbLight(void) { // Syntax is: @@ -768,7 +746,10 @@ void CmndZbLight(void) { ResponseCmndDone(); } +// +// Command `ZbForget` // Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices +// void CmndZbForget(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); @@ -783,12 +764,13 @@ void CmndZbForget(void) { } } +// +// Command `ZbSave` // Save Zigbee information to flash +// void CmndZbSave(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } - saveZigbeeDevices(); - ResponseCmndDone(); } @@ -803,7 +785,7 @@ void CmndZbSave(void) { void CmndZbRestore(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } DynamicJsonBuffer jsonBuf; - const JsonVariant json_parsed = jsonBuf.parse((const char*)XdrvMailbox.data); // const to force a copy of parameter + const JsonVariant json_parsed = jsonBuf.parse((const char*) XdrvMailbox.data); // const to force a copy of parameter const JsonVariant * json = &json_parsed; // root of restore, to be changed if needed bool success = false; @@ -846,14 +828,17 @@ void CmndZbRestore(void) { ResponseCmndDone(); } +// +// Command `ZbRead` // Send an attribute read command to a device, specifying cluster and list of attributes +// void CmndZbRead(void) { - // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} - // ZigbeeRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"} - // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]} + // ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} + // ZbRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"} + // ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]} if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } DynamicJsonBuffer jsonBuf; - JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data); if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params @@ -884,7 +869,7 @@ void CmndZbRead(void) { if (nullptr != &val_attr) { uint16_t val = strToUInt(val_attr); if (val_attr.is()) { - JsonArray& attr_arr = val_attr; + const JsonArray& attr_arr = val_attr.as(); attrs_len = attr_arr.size() * 2; attrs = new uint8_t[attrs_len]; @@ -920,7 +905,10 @@ void CmndZbRead(void) { if (attrs) { delete[] attrs; } } +// +// Command `ZbPermitJoin` // Allow or Deny pairing of new Zigbee devices +// void CmndZbPermitJoin(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint32_t payload = XdrvMailbox.payload; @@ -946,6 +934,9 @@ void CmndZbPermitJoin(void) { ResponseCmndDone(); } +// +// Command `ZbStatus` +// void CmndZbStatus(void) { if (ZigbeeSerial) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } @@ -976,7 +967,7 @@ bool Xdrv23(uint8_t function) } break; case FUNC_LOOP: - if (ZigbeeSerial) { ZigbeeInput(); } + if (ZigbeeSerial) { ZigbeeInputLoop(); } if (zigbee.state_machine) { ZigbeeStateMachine_Run(); }