diff --git a/lib/default/Ext-printf/src/SBuffer.hpp b/lib/default/Ext-printf/src/SBuffer.hpp index 57b662554..67bbbcd48 100644 --- a/lib/default/Ext-printf/src/SBuffer.hpp +++ b/lib/default/Ext-printf/src/SBuffer.hpp @@ -52,6 +52,20 @@ public: delete[] _buf; } + // increase the internal buffer if needed + // do nothing if the buffer is big enough + void reserve(const size_t size) { + if (size > _buf->size) { + // we need to increase the buffer size + SBuffer_impl * new_buf = (SBuffer_impl*) new char[size+4]; // add 4 bytes for size and len + new_buf->size = size; + new_buf->len = _buf->len; + memmove(&new_buf->buf, &_buf->buf, _buf->len); // copy buffer + delete[] _buf; + _buf = new_buf; + } + } + inline void setLen(const size_t len) { uint16_t old_len = _buf->len; _buf->len = (len <= _buf->size) ? len : _buf->size; @@ -118,6 +132,13 @@ public: return _buf->len; } + void replace(const SBuffer &buf2) { + uint32_t len = buf2.len(); + reserve(len); + setLen(0); // clear buffer + addBuffer(buf2); + } + size_t addBuffer(const SBuffer &buf2) { if (len() + buf2.len() <= size()) { for (uint32_t i = 0; i < buf2.len(); i++) { diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index d88bd80d9..de7bbff39 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -28,22 +28,40 @@ // // structure containing all needed information to send a ZCL packet // -class ZigbeeZCLSendMessage { +class ZCLMessage { + public: - uint16_t shortaddr; - uint16_t groupaddr; - uint16_t cluster; - uint8_t endpoint; - uint8_t cmd; - uint16_t manuf; - bool clusterSpecific; - bool needResponse; - bool direct; // true if direct, false if discover router - uint8_t transacId; // ZCL transaction number - const uint8_t *msg; - size_t len; + ZCLMessage(void); // allocate 16 bytes vy default + ZCLMessage(size_t size); + + inline bool validShortaddr(void) const { return BAD_SHORTADDR != shortaddr; } + inline bool validGroupaddr(void) const { return 0 != groupaddr; } + inline bool validCluster(void) const { return 0xFFFF != cluster; } + inline bool validEndpoint(void) const { return 0x00 != endpoint; } + inline bool validCmd(void) const { return 0xFF != cmd; } + + inline void setTransac(uint8_t _transac) { transac = _transac; transacSet = true; } + + uint16_t shortaddr = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid + uint16_t groupaddr = 0x0000; // group address valid only if device == BAD_SHORTADDR + uint16_t cluster = 0xFFFF; // no default + uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint + uint8_t cmd = 0xFF; // 0xFF is invalid command number + uint16_t manuf = 0x0000; // default manuf id + bool clusterSpecific = false; + bool needResponse = true; + bool direct = false; // true if direct, false if discover router + bool transacSet = false; // is transac already set + uint8_t transac = 0; // ZCL transaction number + SBuffer buf; + // const uint8_t *msg = nullptr; + // size_t len = 0; }; +// define constructor seperately to avoid inlining and reduce Flash size +ZCLMessage::ZCLMessage(void) : buf(12) {}; +ZCLMessage::ZCLMessage(size_t size) : buf(size) {}; + typedef int32_t (*ZB_Func)(uint8_t value); typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const SBuffer &buf); @@ -119,8 +137,8 @@ public: struct ZigbeeStatus zigbee; SBuffer *zigbee_buffer = nullptr; -void zigbeeZCLSendCmd(const ZigbeeZCLSendMessage &msg); -void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl); +void zigbeeZCLSendCmd(ZCLMessage &msg); +void ZigbeeZCLSend_Raw(const ZCLMessage &zcl); bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false); // parse Hex formatted attribute names like '0301/0001" diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index 7fa7d657b..9204285ae 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -152,27 +152,21 @@ void ZigbeeHueGroups(String * lights) { } void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) { - zigbeeZCLSendCmd(ZigbeeZCLSendMessage({ - shortaddr, - 0 /* groupaddr */, - cluster /*cluster*/, - 0 /* endpoint */, - cmd /* cmd */, - 0, /* manuf */ - true /* cluster specific */, - true /* response */, - false /* discover route */, - 0, /* zcl transaction id */ - (&s != nullptr) ? s.getBuffer() : nullptr, - (&s != nullptr) ? s.len() : 0 - })); + ZCLMessage zcl(&s ? s.len() : 0); + zcl.shortaddr = shortaddr; + zcl.cluster = cluster; + zcl.cmd = cmd; + zcl.clusterSpecific = true; + zcl.needResponse = true; + zcl.direct = false; // discover route + if (&s) { zcl.buf.replace(s); } + zigbeeZCLSendCmd(zcl); } // Send commands // Power On/Off void ZigbeeHuePower(uint16_t shortaddr, bool power) { ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, *(SBuffer*)nullptr); -// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, ""); zigbee_devices.getShortAddr(shortaddr).setPower(power, 0); } @@ -183,23 +177,16 @@ void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) { s.add8(dimmer); s.add16(0x000A); // transition time = 1s ZigbeeSendHue(shortaddr, 0x0008, 0x04, s); - // char param[8]; - // snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer); - // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param); zigbee_devices.getLight(shortaddr).setDimmer(dimmer); } // CT void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) { if (ct > 0xFEFF) { ct = 0xFEFF; } - // AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct); SBuffer s(4); s.add16(ct); s.add16(0x000A); // transition time = 1s ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s); - // char param[12]; - // snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8); - // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); light.setColorMode(2); // "ct" light.setCT(ct); @@ -214,10 +201,6 @@ void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) { s.add16(y); s.add16(0x000A); // transition time = 1s ZigbeeSendHue(shortaddr, 0x0300, 0x07, s); - // char param[16]; - // snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8); - // uint8_t colormode = 1; // "xy" - // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); light.setColorMode(1); // "xy" light.setX(x); @@ -233,10 +216,6 @@ void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) { s.add8(sat); s.add16(0); ZigbeeSendHue(shortaddr, 0x0300, 0x06, s); - // char param[16]; - // snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat); - // uint8_t colormode = 0; // "hs" - // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); light.setColorMode(0); // "hs" light.setSat(sat); diff --git a/tasmota/xdrv_23_zigbee_5__constants.ino b/tasmota/xdrv_23_zigbee_5__constants.ino index fc8b95661..7118eaad7 100644 --- a/tasmota/xdrv_23_zigbee_5__constants.ino +++ b/tasmota/xdrv_23_zigbee_5__constants.ino @@ -381,7 +381,6 @@ const char Z_strings[] PROGMEM = "ProductURL" "\x00" "QualityMeasure" "\x00" "RGB" "\x00" - "RGBb" "\x00" "RMSCurrent" "\x00" "RMSVoltage" "\x00" "ReactivePower" "\x00" @@ -474,6 +473,7 @@ const char Z_strings[] PROGMEM = "ZoneStatus" "\x00" "ZoneStatusChange" "\x00" "ZoneType" "\x00" + "_" "\x00" "xx" "\x00" "xx000A00" "\x00" "xx0A" "\x00" @@ -805,121 +805,121 @@ enum Z_offsets { Zo_ProductURL = 4956, Zo_QualityMeasure = 4967, Zo_RGB = 4982, - Zo_RGBb = 4986, - Zo_RMSCurrent = 4991, - Zo_RMSVoltage = 5002, - Zo_ReactivePower = 5013, - Zo_RecallScene = 5027, - Zo_RemainingTime = 5039, - Zo_RemoteSensing = 5053, - Zo_RemoveAllGroups = 5067, - Zo_RemoveAllScenes = 5083, - Zo_RemoveGroup = 5099, - Zo_RemoveScene = 5111, - Zo_ResetAlarm = 5123, - Zo_ResetAllAlarms = 5134, - Zo_SWBuildID = 5149, - Zo_Sat = 5159, - Zo_SatMove = 5163, - Zo_SatStep = 5171, - Zo_SceneCount = 5179, - Zo_SceneValid = 5190, - Zo_ScheduleMode = 5201, - Zo_SeaPressure = 5214, - Zo_ShortPollInterval = 5226, - Zo_Shutter = 5244, - Zo_ShutterClose = 5252, - Zo_ShutterLift = 5265, - Zo_ShutterOpen = 5277, - Zo_ShutterStop = 5289, - Zo_ShutterTilt = 5301, - Zo_SoftwareRevision = 5313, - Zo_StackVersion = 5330, - Zo_StandardTime = 5343, - Zo_StartUpOnOff = 5356, - Zo_Status = 5369, - Zo_StoreScene = 5376, - Zo_SwitchType = 5387, - Zo_SystemMode = 5398, - Zo_TRVBoost = 5409, - Zo_TRVChildProtection = 5418, - Zo_TRVMirrorDisplay = 5437, - Zo_TRVMode = 5454, - Zo_TRVWindowOpen = 5462, - Zo_TempTarget = 5476, - Zo_Temperature = 5487, - Zo_TemperatureMaxMeasuredValue = 5499, - Zo_TemperatureMinMeasuredValue = 5527, - Zo_TemperatureTolerance = 5555, - Zo_TerncyDuration = 5576, - Zo_TerncyRotate = 5591, - Zo_ThSetpoint = 5604, - Zo_Time = 5615, - Zo_TimeEpoch = 5620, - Zo_TimeStatus = 5630, - Zo_TimeZone = 5641, - Zo_TotalProfileNum = 5650, - Zo_TuyaAutoLock = 5666, - Zo_TuyaAwayDays = 5679, - Zo_TuyaAwayTemp = 5692, - Zo_TuyaBattery = 5705, - Zo_TuyaBoostTime = 5717, - Zo_TuyaChildLock = 5731, - Zo_TuyaComfortTemp = 5745, - Zo_TuyaEcoTemp = 5761, - Zo_TuyaFanMode = 5773, - Zo_TuyaForceMode = 5785, - Zo_TuyaMaxTemp = 5799, - Zo_TuyaMinTemp = 5811, - Zo_TuyaPreset = 5823, - Zo_TuyaScheduleHolidays = 5834, - Zo_TuyaScheduleWorkdays = 5855, - Zo_TuyaTempTarget = 5876, - Zo_TuyaValveDetection = 5891, - Zo_TuyaValvePosition = 5910, - Zo_TuyaWeekSelect = 5928, - Zo_TuyaWindowDetection = 5943, - Zo_UnoccupiedCoolingSetpoint = 5963, - Zo_UnoccupiedHeatingSetpoint = 5989, - Zo_UtilityName = 6015, - Zo_ValidUntilTime = 6027, - Zo_ValvePosition = 6042, - Zo_VelocityLift = 6056, - Zo_ViewGroup = 6069, - Zo_ViewScene = 6079, - Zo_Water = 6089, - Zo_WhitePointX = 6095, - Zo_WhitePointY = 6107, - Zo_WindowCoveringType = 6119, - Zo_X = 6138, - Zo_Y = 6140, - Zo_ZCLVersion = 6142, - Zo_ZoneState = 6153, - Zo_ZoneStatus = 6163, - Zo_ZoneStatusChange = 6174, - Zo_ZoneType = 6191, - Zo_xx = 6200, - Zo_xx000A00 = 6203, - Zo_xx0A = 6212, - Zo_xx0A00 = 6217, - Zo_xx19 = 6224, - Zo_xx190A = 6229, - Zo_xx190A00 = 6236, - Zo_xxxx = 6245, - Zo_xxxx00 = 6250, - Zo_xxxx0A00 = 6257, - Zo_xxxxyy = 6266, - Zo_xxxxyyyy = 6273, - Zo_xxxxyyyy0A00 = 6282, - Zo_xxxxyyzz = 6295, - Zo_xxyy = 6304, - Zo_xxyy0A00 = 6309, - Zo_xxyyyy = 6318, - Zo_xxyyyy000000000000 = 6325, - Zo_xxyyyy0A0000000000 = 6344, - Zo_xxyyyyzz = 6363, - Zo_xxyyyyzzzz = 6372, - Zo_xxyyzzzz = 6383, + Zo_RMSCurrent = 4986, + Zo_RMSVoltage = 4997, + Zo_ReactivePower = 5008, + Zo_RecallScene = 5022, + Zo_RemainingTime = 5034, + Zo_RemoteSensing = 5048, + Zo_RemoveAllGroups = 5062, + Zo_RemoveAllScenes = 5078, + Zo_RemoveGroup = 5094, + Zo_RemoveScene = 5106, + Zo_ResetAlarm = 5118, + Zo_ResetAllAlarms = 5129, + Zo_SWBuildID = 5144, + Zo_Sat = 5154, + Zo_SatMove = 5158, + Zo_SatStep = 5166, + Zo_SceneCount = 5174, + Zo_SceneValid = 5185, + Zo_ScheduleMode = 5196, + Zo_SeaPressure = 5209, + Zo_ShortPollInterval = 5221, + Zo_Shutter = 5239, + Zo_ShutterClose = 5247, + Zo_ShutterLift = 5260, + Zo_ShutterOpen = 5272, + Zo_ShutterStop = 5284, + Zo_ShutterTilt = 5296, + Zo_SoftwareRevision = 5308, + Zo_StackVersion = 5325, + Zo_StandardTime = 5338, + Zo_StartUpOnOff = 5351, + Zo_Status = 5364, + Zo_StoreScene = 5371, + Zo_SwitchType = 5382, + Zo_SystemMode = 5393, + Zo_TRVBoost = 5404, + Zo_TRVChildProtection = 5413, + Zo_TRVMirrorDisplay = 5432, + Zo_TRVMode = 5449, + Zo_TRVWindowOpen = 5457, + Zo_TempTarget = 5471, + Zo_Temperature = 5482, + Zo_TemperatureMaxMeasuredValue = 5494, + Zo_TemperatureMinMeasuredValue = 5522, + Zo_TemperatureTolerance = 5550, + Zo_TerncyDuration = 5571, + Zo_TerncyRotate = 5586, + Zo_ThSetpoint = 5599, + Zo_Time = 5610, + Zo_TimeEpoch = 5615, + Zo_TimeStatus = 5625, + Zo_TimeZone = 5636, + Zo_TotalProfileNum = 5645, + Zo_TuyaAutoLock = 5661, + Zo_TuyaAwayDays = 5674, + Zo_TuyaAwayTemp = 5687, + Zo_TuyaBattery = 5700, + Zo_TuyaBoostTime = 5712, + Zo_TuyaChildLock = 5726, + Zo_TuyaComfortTemp = 5740, + Zo_TuyaEcoTemp = 5756, + Zo_TuyaFanMode = 5768, + Zo_TuyaForceMode = 5780, + Zo_TuyaMaxTemp = 5794, + Zo_TuyaMinTemp = 5806, + Zo_TuyaPreset = 5818, + Zo_TuyaScheduleHolidays = 5829, + Zo_TuyaScheduleWorkdays = 5850, + Zo_TuyaTempTarget = 5871, + Zo_TuyaValveDetection = 5886, + Zo_TuyaValvePosition = 5905, + Zo_TuyaWeekSelect = 5923, + Zo_TuyaWindowDetection = 5938, + Zo_UnoccupiedCoolingSetpoint = 5958, + Zo_UnoccupiedHeatingSetpoint = 5984, + Zo_UtilityName = 6010, + Zo_ValidUntilTime = 6022, + Zo_ValvePosition = 6037, + Zo_VelocityLift = 6051, + Zo_ViewGroup = 6064, + Zo_ViewScene = 6074, + Zo_Water = 6084, + Zo_WhitePointX = 6090, + Zo_WhitePointY = 6102, + Zo_WindowCoveringType = 6114, + Zo_X = 6133, + Zo_Y = 6135, + Zo_ZCLVersion = 6137, + Zo_ZoneState = 6148, + Zo_ZoneStatus = 6158, + Zo_ZoneStatusChange = 6169, + Zo_ZoneType = 6186, + Zo__ = 6195, + Zo_xx = 6197, + Zo_xx000A00 = 6200, + Zo_xx0A = 6209, + Zo_xx0A00 = 6214, + Zo_xx19 = 6221, + Zo_xx190A = 6226, + Zo_xx190A00 = 6233, + Zo_xxxx = 6242, + Zo_xxxx00 = 6247, + Zo_xxxx0A00 = 6254, + Zo_xxxxyy = 6263, + Zo_xxxxyyyy = 6270, + Zo_xxxxyyyy0A00 = 6279, + Zo_xxxxyyzz = 6292, + Zo_xxyy = 6301, + Zo_xxyy0A00 = 6306, + Zo_xxyyyy = 6315, + Zo_xxyyyy000000000000 = 6322, + Zo_xxyyyy0A0000000000 = 6341, + Zo_xxyyyyzz = 6360, + Zo_xxyyyyzzzz = 6369, + Zo_xxyyzzzz = 6380, }; diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index f91b50498..04209ad78 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1209,23 +1209,19 @@ void ZCLFrame::parseReportAttributes(Z_attribute_list& attr_list) { // The sensor expects the coordinator to send a Default Response to acknowledge the attribute reporting if (0 == _frame_control.b.disable_def_resp) { // the device expects a default response - SBuffer buf(2); - buf.add8(_cmd_id); - buf.add8(0x00); // Status = OK - - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - _srcaddr, - 0x0000, - _cluster_id, - _srcendpoint, - ZCL_DEFAULT_RESPONSE, - _manuf_code, - false /* not cluster specific */, - false /* noresponse */, - true /* direct no retry */, - _transact_seq, /* zcl transaction id */ - buf.getBuffer(), buf.len() - })); + ZCLMessage zcl(2); // message is 2 bytes + zcl.shortaddr = _srcaddr; + zcl.cluster = _cluster_id; + zcl.endpoint = _srcendpoint; + zcl.cmd = ZCL_DEFAULT_RESPONSE; + zcl.manuf = _manuf_code; + zcl.clusterSpecific = false; /* not cluster specific */ + zcl.needResponse = false; /* noresponse */ + zcl.direct = true; /* direct no retry */ + zcl.setTransac(_transact_seq); + zcl.buf.add8(_cmd_id); + zcl.buf.add8(0); // Status = OK + zigbeeZCLSendCmd(zcl); } } @@ -1674,23 +1670,19 @@ void ZCLFrame::parseClusterSpecificCommand(Z_attribute_list& attr_list) { // Send Default Response to acknowledge the attribute reporting if (0 == _frame_control.b.disable_def_resp) { // the device expects a default response - SBuffer buf(2); - buf.add8(_cmd_id); - buf.add8(0x00); // Status = OK - - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - _srcaddr, - 0x0000, - _cluster_id, - _srcendpoint, - ZCL_DEFAULT_RESPONSE, - _manuf_code, - false /* not cluster specific */, - false /* noresponse */, - true /* direct no retry */, - _transact_seq, /* zcl transaction id */ - buf.getBuffer(), buf.len() - })); + ZCLMessage zcl(2); // message is 4 bytes + zcl.shortaddr = _srcaddr; + zcl.cluster = _cluster_id; + zcl.endpoint = _srcendpoint; + zcl.cmd = ZCL_DEFAULT_RESPONSE; + zcl.manuf = _manuf_code; + zcl.clusterSpecific = false; /* not cluster specific */ + zcl.needResponse = false; /* noresponse */ + zcl.direct = true; /* direct no retry */ + zcl.setTransac(_transact_seq); + zcl.buf.add8(_cmd_id); + zcl.buf.add8(0x00); // Status = OK + zigbeeZCLSendCmd(zcl); } } diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index ea0196368..1f4c02bfc 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -46,7 +46,7 @@ typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z // - cluster: cluster number of the command // - cmd: the command number, of 0xFF if it's actually a variable to be assigned from 'xx' // - direction: the direction of the command (bit field). 0x01=from client to server (coord to device), 0x02= from server to client (response), 0x80=needs specific decoding -// - param: the paylod template, x/y/z are substituted with arguments, little endian. For command display, payload must match until x/y/z character or until the end of the paylod. '??' means ignore. +// - param: the paylod template, x/y/z are substituted with arguments, little endian. For command display, payload must match until x/y/z character or until the end of the paylod. '_' means custom converter. const Z_CommandConverter Z_Commands[] PROGMEM = { // Identify cluster { Z_(Identify), 0x0003, 0x00, 0x01, Z_(xxxx) }, // Identify device, time in seconds @@ -82,7 +82,7 @@ const Z_CommandConverter Z_Commands[] PROGMEM = { { Z_(HueSat), 0x0300, 0x06, 0x01, Z_(xxyy0A00) }, // Hue, Sat { Z_(Color), 0x0300, 0x07, 0x01, Z_(xxxxyyyy0A00) }, // x, y (uint16) { Z_(CT), 0x0300, 0x0A, 0x01, Z_(xxxx0A00) }, // Color Temperature Mireds (uint16) - { Z_(RGB), 0x0300, 0xF0, 0x81, Z_() }, // synthetic commands converting RGB to XY + { Z_(RGB), 0x0300, 0xF0, 0x81, Z_(_) }, // synthetic commands converting RGB to XY { Z_(ShutterOpen), 0x0102, 0x00, 0x01, Z_() }, { Z_(ShutterClose), 0x0102, 0x01, 0x01, Z_() }, { Z_(ShutterStop), 0x0102, 0x02, 0x01, Z_() }, @@ -180,20 +180,18 @@ void Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster if (groupaddr) { shortaddr = BAD_SHORTADDR; // if group address, don't send to device } - uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr); - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - groupaddr, - cluster /*cluster*/, - endpoint, - ZCL_READ_ATTRIBUTES, - 0, /* manuf */ - false /* not cluster specific */, - true /* response */, - false /* discover route */, - seq, /* zcl transaction id */ - attrs, attrs_len - })); + + ZCLMessage zcl(attrs_len); // message is `attrs_len` bytes + zcl.shortaddr = shortaddr; + zcl.groupaddr = groupaddr; + zcl.cluster = cluster; + zcl.endpoint = endpoint; + zcl.cmd = ZCL_READ_ATTRIBUTES; + zcl.clusterSpecific = false; + zcl.needResponse = true; + zcl.direct = false; // discover route + zcl.buf.addBuffer(attrs, attrs_len); + zigbeeZCLSendCmd(zcl); } } @@ -535,17 +533,19 @@ bool convertTuyaSpecificCluster(class Z_attribute_list &attr_list, uint16_t clus // Only take commands outgoing, i.e. direction == 0 // If not found: // - returns nullptr -const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) { +// - return PROGMEM string +const char * zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) { if (nullptr == command) { return nullptr; } for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) { const Z_CommandConverter *conv = &Z_Commands[i]; uint8_t conv_direction = pgm_read_byte(&conv->direction); uint8_t conv_cmd = pgm_read_byte(&conv->cmd); uint16_t conv_cluster = pgm_read_word(&conv->cluster); + // conv_direction must be client (coord) -> server (device), we can only send commands to end devices if ((conv_direction & 0x01) && (0 == strcasecmp_P(command, Z_strings + pgm_read_word(&conv->tasmota_cmd_offset)))) { *cluster = conv_cluster; *cmd = conv_cmd; - return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->param_offset)); + return Z_strings + pgm_read_word(&conv->param_offset); } } @@ -559,16 +559,17 @@ inline char hexDigit(uint32_t h) { } // replace all xx/yy/zz substrings with unsigned ints, and the corresponding len (8, 16 or 32 bits) -String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) { - size_t len = strlen_P(zcl_cmd_P); - char zcl_cmd[len+1]; - strcpy_P(zcl_cmd, zcl_cmd_P); // copy into RAM +// Returns a SBuffer allocated object, it is the caller's responsibility to delete it +void zigbeeCmdAddParams(SBuffer & buf, const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) { + size_t hex_len = strlen_P(zcl_cmd_P); + buf.reserve((hex_len + 1)/2); - char *p = zcl_cmd; - while (*p) { - if (isXYZ(*p) && (*p == *(p+1))) { // if char is [x-z] and followed by same char + const char * p = zcl_cmd_P; + char c0, c1; + while ((c0 = pgm_read_byte(p)) && (c1 = pgm_read_byte(p+1))) { + if (isXYZ(c0) && (c0 == c1)) { // if char is [x-z] and followed by same char uint8_t val = 0; - switch (*p) { + switch (pgm_read_byte(p)) { case 'x': val = x & 0xFF; x = x >> 8; @@ -582,15 +583,18 @@ String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_ z = z >> 8; break; } - *p = hexDigit(val >> 4); - *(p+1) = hexDigit(val); - p++; + buf.add8(val); + // *p = hexDigit(val >> 4); + // *(p+1) = hexDigit(val); + } else { + char hex[4]; + hex[0] = c0; + hex[1] = c1; + hex[2] = 0; + buf.add8(strtoul(hex, nullptr, 16) & 0xFF); } - p++; + p += 2; } - AddLog_P(LOG_LEVEL_DEBUG, PSTR("SendZCLCommand_P: zcl_cmd = %s"), zcl_cmd); - - return String(zcl_cmd); } const char kZ_Alias[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" "OPEN" "|" // 0 diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index a714d01af..b10129f72 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -1319,78 +1319,55 @@ void Z_SendSimpleDescReq(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluste // // Send AF Info Request // Queue requests for the device -// 1. Request for 'ModelId' and 'Manufacturer': 0000/0005, 0000/0006 +// 1. Request for 'ModelId' and 'Manufacturer': 0000/0005, 0000/0004 // 2. Auto-bind to coordinator: // Iterate among // void Z_SendDeviceInfoRequest(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 InfoReq[] = { 0x04, 0x00, 0x05, 0x00 }; - - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - 0x0000, /* group */ - 0x0000 /*cluster*/, - endpoint, - ZCL_READ_ATTRIBUTES, - 0x0000, /* manuf */ - false /* not cluster specific */, - true /* response */, - false /* discover route */, - transacid, /* zcl transaction id */ - InfoReq, sizeof(InfoReq) - })); + ZCLMessage zcl(4); // message is 4 bytes + zcl.shortaddr = shortaddr; + zcl.cluster = 0; + zcl.cmd = ZCL_READ_ATTRIBUTES; + zcl.clusterSpecific = false; + zcl.needResponse = true; + zcl.direct = false; // discover route + zcl.buf.add16(0x0005); + zcl.buf.add16(0x0004); + zigbeeZCLSendCmd(zcl); } // // Send single attribute read request in Timer // void Z_SendSingleAttributeRead(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { - uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); - uint8_t InfoReq[2] = { Z_B0(value), Z_B1(value) }; // list of single attribute - - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - 0x0000, /* group */ - cluster /*cluster*/, - endpoint, - ZCL_READ_ATTRIBUTES, - 0x0000, /* manuf */ - false /* not cluster specific */, - true /* response */, - false /* discover route */, - transacid, /* zcl transaction id */ - InfoReq, sizeof(InfoReq) - })); + ZCLMessage zcl(2); // message is 2 bytes + zcl.shortaddr = shortaddr; + zcl.cluster = cluster; + zcl.endpoint = endpoint; + zcl.cmd = ZCL_READ_ATTRIBUTES; + zcl.clusterSpecific = false; + zcl.needResponse = true; + zcl.direct = false; // discover route + zcl.buf.add16(value); // 04000500 + zigbeeZCLSendCmd(zcl); } // // Write CIE address // void Z_WriteCIEAddress(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { - uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); - SBuffer buf(12); - buf.add16(0x0010); // attribute 0x0010 - buf.add8(ZEUI64); - buf.add64(localIEEEAddr); - - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Writing CIE address")); - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - 0x0000, /* group */ - 0x0500 /*cluster*/, - endpoint, - ZCL_WRITE_ATTRIBUTES, - 0x0000, /* manuf */ - false /* not cluster specific */, - true /* response */, - false /* discover route */, - transacid, /* zcl transaction id */ - buf.getBuffer(), buf.len() - })); + ZCLMessage zcl(12); // message is 12 bytes + zcl.shortaddr = shortaddr; + zcl.cluster = 0x0500; + zcl.endpoint = endpoint; + zcl.cmd = ZCL_WRITE_ATTRIBUTES; + zcl.clusterSpecific = false; + zcl.needResponse = true; + zcl.direct = false; // discover route + zcl.buf.add16(0x0010); // attribute 0x0010 + zcl.buf.add8(ZEUI64); + zcl.buf.add64(localIEEEAddr); + zigbeeZCLSendCmd(zcl); } @@ -1398,23 +1375,18 @@ void Z_WriteCIEAddress(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, // Write CIE address // void Z_SendCIEZoneEnrollResponse(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { - uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); - uint8_t EnrollRSP[2] = { 0x00 /* Sucess */, Z_B0(value) /* ZoneID */ }; - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Sending Enroll Zone %d"), Z_B0(value)); - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - 0x0000, /* group */ - 0x0500 /*cluster*/, - endpoint, - 0x00, // Zone Enroll Response - 0x0000, /* manuf */ - true /* cluster specific */, - true /* response */, - false /* discover route */, - transacid, /* zcl transaction id */ - EnrollRSP, sizeof(EnrollRSP) - })); + ZCLMessage zcl(2); // message is 2 bytes + zcl.shortaddr = shortaddr; + zcl.cluster = 0x0500; + zcl.endpoint = endpoint; + zcl.cmd = 0x00, // Zone Enroll Response + zcl.clusterSpecific = true; + zcl.needResponse = true; + zcl.direct = false; // discover route + zcl.buf.add8(0x00); // success + zcl.buf.add8(Z_B0(value)); // ZoneID + zigbeeZCLSendCmd(zcl); } // @@ -1549,19 +1521,16 @@ void Z_AutoConfigReportingForCluster(uint16_t shortaddr, uint16_t groupaddr, uin if (buf.len() > 0) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `%s`"), TasmotaGlobal.mqtt_data); - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - shortaddr, - 0x0000, /* group */ - cluster /*cluster*/, - endpoint, - ZCL_CONFIGURE_REPORTING, - 0x0000, /* manuf */ - false /* not cluster specific */, - false /* no response */, - false /* discover route */, - zigbee_devices.getNextSeqNumber(shortaddr), /* zcl transaction id */ - buf.buf(), buf.len() - })); + ZCLMessage zcl(buf.len()); // message is 4 bytes + zcl.shortaddr = shortaddr; + zcl.cluster = cluster; + zcl.endpoint = endpoint; + zcl.cmd = ZCL_CONFIGURE_REPORTING; + zcl.clusterSpecific = false; /* not cluster specific */ + zcl.needResponse = false; /* noresponse */ + zcl.direct = false; /* discover route */ + zcl.buf.addBuffer(buf); + zigbeeZCLSendCmd(zcl); } } @@ -2149,19 +2118,17 @@ void ZCLFrame::autoResponder(const uint16_t *attr_list_ids, size_t attr_len) { // send // all good, send the packet - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ - _srcaddr, - 0x0000, - _cluster_id /*cluster*/, - _srcendpoint, - ZCL_READ_ATTRIBUTES_RESPONSE, - 0x0000, /* manuf */ - false /* not cluster specific */, - false /* no response */, - true /* direct response */, - _transact_seq, /* zcl transaction id */ - buf.getBuffer(), buf.len() - })); + ZCLMessage zcl(buf.len()); // message is 4 bytes + zcl.shortaddr = _srcaddr; + zcl.cluster = _cluster_id; + zcl.endpoint = _srcendpoint; + zcl.cmd = ZCL_READ_ATTRIBUTES_RESPONSE; + zcl.clusterSpecific = false; /* not cluster specific */ + zcl.needResponse = false; /* noresponse */ + zcl.direct = true; /* direct response */ + zcl.setTransac(_transact_seq); + zcl.buf.addBuffer(buf); + zigbeeZCLSendCmd(zcl); } } diff --git a/tasmota/xdrv_23_zigbee_9_serial.ino b/tasmota/xdrv_23_zigbee_9_serial.ino index 84a560703..463782ad7 100644 --- a/tasmota/xdrv_23_zigbee_9_serial.ino +++ b/tasmota/xdrv_23_zigbee_9_serial.ino @@ -748,16 +748,17 @@ void CmndZbEZSPSend(void) // - 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 +// - transac: 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(const ZigbeeZCLSendMessage &zcl) { + +void ZigbeeZCLSend_Raw(const ZCLMessage &zcl) { + SBuffer buf(32+zcl.buf.len()); #ifdef USE_ZIGBEE_ZNP - SBuffer buf(32+zcl.len); buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(AF_DATA_REQUEST_EXT); // 02 - if (BAD_SHORTADDR == zcl.shortaddr) { // if no shortaddr we assume group address + if (!zcl.validShortaddr()) { // if no shortaddr we assume group address buf.add8(Z_Addr_Group); // 01 buf.add64(zcl.groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded buf.add8(0xFF); // dest endpoint is not used for group addresses @@ -769,28 +770,24 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) { buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan buf.add8(0x01); // source endpoint buf.add16(zcl.cluster); - buf.add8(zcl.transacId); // transacId + buf.add8(zcl.transac); // transac buf.add8(0x30); // 30 options buf.add8(0x1E); // 1E radius - buf.add16(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add16(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0)); buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field if (zcl.manuf) { buf.add16(zcl.manuf); // add Manuf Id if not null } - buf.add8(zcl.transacId); // Transaction Sequence Number + buf.add8(zcl.transac); // Transaction Sequence Number buf.add8(zcl.cmd); - if (zcl.len > 0) { - buf.addBuffer(zcl.msg, zcl.len); // add the payload - } + buf.addBuffer(zcl.buf); ZigbeeZNPSend(buf.getBuffer(), buf.len()); #endif // USE_ZIGBEE_ZNP #ifdef USE_ZIGBEE_EZSP - SBuffer buf(32+zcl.len); - - if (BAD_SHORTADDR != zcl.shortaddr) { + if (zcl.validShortaddr()) { // send unicast message to an address buf.add16(EZSP_sendUnicast); // 3400 buf.add8(EMBER_OUTGOING_DIRECT); // 00 @@ -806,20 +803,18 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) { buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame } buf.add16(zcl.groupaddr); // groupId - buf.add8(zcl.transacId); + buf.add8(zcl.transac); // end of ApsFrame buf.add8(0x01); // tag TODO - buf.add8(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add8(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0)); buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field if (zcl.manuf) { buf.add16(zcl.manuf); // add Manuf Id if not null } - buf.add8(zcl.transacId); // Transaction Sequance Number + buf.add8(zcl.transac); // Transaction Sequance Number buf.add8(zcl.cmd); - if (zcl.len > 0) { - buf.addBuffer(zcl.msg, zcl.len); // add the payload - } + buf.addBuffer(zcl.buf); } else { // send broadcast group address, aka groupcast buf.add16(EZSP_sendMulticast); // 3800 @@ -834,22 +829,20 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) { buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame } buf.add16(zcl.groupaddr); // groupId - buf.add8(zcl.transacId); + buf.add8(zcl.transac); // end of ApsFrame buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS buf.add8(7); // nonMemberRadius, 7 = infinite buf.add8(0x01); // tag TODO - buf.add8(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add8(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0)); buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field if (zcl.manuf) { buf.add16(zcl.manuf); // add Manuf Id if not null } - buf.add8(zcl.transacId); // Transaction Sequance Number + buf.add8(zcl.transac); // Transaction Sequance Number buf.add8(zcl.cmd); - if (zcl.len > 0) { - buf.addBuffer(zcl.msg, zcl.len); // add the payload - } + buf.addBuffer(zcl.buf); } ZigbeeEZSPSendCmd(buf.buf(), buf.len()); diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 3c7e2f49c..540b32278 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -179,59 +179,39 @@ void CmndZbReset(void) { // - 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 manuf, - uint16_t cluster, uint8_t cmd, const char *param) { - size_t size = param ? strlen(param) : 0; - SBuffer buf((size+2)/2); // actual bytes buffer for data - - if (param) { - while (*param) { - uint8_t code = parseHex_P(¶m, 2); - buf.add8(code); - } - } - - zigbeeZCLSendCmd(ZigbeeZCLSendMessage({ - shortaddr, - groupaddr, - cluster /*cluster*/, - endpoint, - cmd, - manuf, /* manuf */ - clusterSpecific /* not cluster specific */, - true /* response */, - false /* discover route */, - 0, /* zcl transaction id */ - buf.getBuffer(), buf.len() - })); -} - -void zigbeeZCLSendCmd(const class ZigbeeZCLSendMessage &msg_const) { - ZigbeeZCLSendMessage msg = msg_const; // copy to a modifiable variable - - if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) { +void zigbeeZCLSendCmd(class ZCLMessage &zcl) { + if ((0 == zcl.endpoint) && (zcl.validShortaddr())) { // endpoint is not specified, let's try to find it from shortAddr, unless it's a group address - msg.endpoint = zigbee_devices.findFirstEndpoint(msg.shortaddr); + zcl.endpoint = zigbee_devices.findFirstEndpoint(zcl.shortaddr); + if (0x00 == zcl.endpoint) { zcl.endpoint = 0x01; } // if we don't know the endpoint, try 0x01 //AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); } - AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %*_H"), - msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint, msg.cmd, msg.len, msg.msg); + // AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %_B"), + // zcl.shortaddr, zcl.groupaddr, zcl.cluster, zcl.endpoint, zcl.cmd, &zcl.buf); - if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) { // endpoint null is ok for group address + if ((0 == zcl.endpoint) && (zcl.validShortaddr())) { // endpoint null is ok for group address AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); return; } // everything is good, we can send the command - msg.transacId = zigbee_devices.getNextSeqNumber(msg.shortaddr); - ZigbeeZCLSend_Raw(msg); + if (!zcl.transacSet) { + zcl.transac = zigbee_devices.getNextSeqNumber(zcl.shortaddr); + zcl.transacSet = true; + } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%_B\""), + zcl.shortaddr, zcl.groupaddr, zcl.endpoint, zcl.cluster, zcl.cmd, &zcl.buf); + + ZigbeeZCLSend_Raw(zcl); + // now set the timer, if any, to read back the state later - if (msg.clusterSpecific) { + if (zcl.clusterSpecific) { if (!Settings.flag5.zb_disable_autoquery) { // read back attribute value unless it is disabled - sendHueUpdate(msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint); + sendHueUpdate(zcl.shortaddr, zcl.groupaddr, zcl.cluster, zcl.endpoint); } } } @@ -342,14 +322,15 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat // Parse "Report", "Write", "Response" or "Config" attribute // Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01) // -void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMessage & packet) { - SBuffer buf(200); // buffer to store the binary output of attibutes +void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLMessage & zcl) { + zcl.buf.reserve(200); // buffer to store the binary output of attibutes + SBuffer & buf = zcl.buf; // synonym if (nullptr == XdrvMailbox.command) { XdrvMailbox.command = (char*) ""; // prevent a crash when calling ReponseCmndChar and there was no previous command } - bool tuya_protocol = zigbee_devices.isTuyaProtocol(packet.shortaddr, packet.endpoint); + bool tuya_protocol = zigbee_devices.isTuyaProtocol(zcl.shortaddr, zcl.endpoint); // iterate on keys for (auto key : val_pubwrite.getObject()) { @@ -361,9 +342,9 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe // Buffer ready, do some sanity checks // all attributes must use the same cluster - if (0xFFFF == packet.cluster) { - packet.cluster = attr.key.id.cluster; // set the cluster for this packet - } else if (packet.cluster != attr.key.id.cluster) { + if (0xFFFF == zcl.cluster) { + zcl.cluster = attr.key.id.cluster; // set the cluster for this packet + } else if (zcl.cluster != attr.key.id.cluster) { ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); return; } @@ -390,20 +371,20 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe const char* val_str = ""; // variant as string //////////////////////////////////////////////////////////////////////////////// // Split encoding depending on message - if (packet.cmd != ZCL_CONFIGURE_REPORTING) { - if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) { + if (zcl.cmd != ZCL_CONFIGURE_REPORTING) { + if ((zcl.cluster == 0XEF00) && (zcl.cmd == ZCL_WRITE_ATTRIBUTES)) { // special case of Tuya / Moes / Lidl attributes if (buf.len() == 0) { // add the prefix to data buf.add8(0); // status - buf.add8(zigbee_devices.getNextSeqNumber(packet.shortaddr)); + buf.add8(zigbee_devices.getNextSeqNumber(zcl.shortaddr)); } - packet.clusterSpecific = true; - packet.cmd = 0x00; + zcl.clusterSpecific = true; + zcl.cmd = 0x00; if (!ZbTuyaWrite(buf, attr)) { return; // error } - } else if (!ZbAppendWriteBuf(buf, attr, packet.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case + } else if (!ZbAppendWriteBuf(buf, attr, zcl.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case return; // error } } else { @@ -464,19 +445,13 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe } // all good, send the packet - packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr); - packet.msg = buf.getBuffer(); - packet.len = buf.len(); - ZigbeeZCLSend_Raw(packet); + zigbeeZCLSendCmd(zcl); ResponseCmndDone(); } // Parse the "Send" attribute and send the command -void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) { - uint8_t cmd = 0; - String cmd_str = ""; // the actual low-level command, either specified or computed - const char *cmd_s = ""; // pointer to payload string - bool clusterSpecific = true; +void ZbSendSend(class JsonParserToken val_cmd, ZCLMessage & zcl) { + zcl.clusterSpecific = true; static const char delim[] = ", "; // delimiters for parameters // probe the type of the argument @@ -497,17 +472,16 @@ void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupad uint16_t cmd_var; uint16_t local_cluster_id; - const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var); - if (tasmota_cmd) { - cmd_str = tasmota_cmd; - } else { + const char * tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var); + if (tasmota_cmd == nullptr) { // did we find the command? Response_P(PSTR(D_ZIGBEE_UNRECOGNIZED_COMMAND), key.getStr()); return; } + // check cluster - if (0xFFFF == cluster) { - cluster = local_cluster_id; - } else if (cluster != local_cluster_id) { + if (0xFFFF == zcl.cluster) { + zcl.cluster = local_cluster_id; + } else if (zcl.cluster != local_cluster_id) { ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); return; } @@ -546,64 +520,62 @@ void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupad //AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); if (0xFF == cmd_var) { // if command number is a variable, replace it with x - cmd = x; + zcl.cmd = x; x = y; // and shift other variables y = z; } else { - cmd = cmd_var; // or simply copy the cmd number + zcl.cmd = cmd_var; // or simply copy the cmd number } - cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters - //AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); - cmd_s = cmd_str.c_str(); + zigbeeCmdAddParams(zcl.buf, tasmota_cmd, x, y, z); // fill in parameters } else { // we have zero command, pass through until last error for missing command + return; } + zigbeeZCLSendCmd(zcl); } else if (val_cmd.isStr()) { // low-level command // Now parse the string to extract cluster, command, and payload // Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC" - // where AA is the cluster number, BBBB the command number, CCCC... the payload + // where AAAA is the cluster number, BB the command number, CCCC... the payload // First delimiter is '_' for a global command, or '!' for a cluster specific command const char * data = val_cmd.getStr(); uint16_t local_cluster_id = parseHex(&data, 4); // check cluster - if (0xFFFF == cluster) { - cluster = local_cluster_id; - } else if (cluster != local_cluster_id) { + if (0xFFFF == zcl.cluster) { + zcl.cluster = local_cluster_id; + } else if (zcl.cluster != local_cluster_id) { ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); return; } // delimiter if (('_' == *data) || ('!' == *data)) { - if ('_' == *data) { clusterSpecific = false; } + if ('_' == *data) { zcl.clusterSpecific = false; } data++; } else { ResponseCmndChar_P(PSTR(D_ZIGBEE_WRONG_DELIMITER)); return; } // parse cmd number - cmd = parseHex(&data, 2); + zcl.cmd = parseHex(&data, 2); // move to end of payload // delimiter is optional if ('/' == *data) { data++; } // skip delimiter - cmd_s = data; + zcl.buf.replace(SBuffer::SBufferFromHex(data, strlen(data))); + zigbeeZCLSendCmd(zcl); } else { - // we have an unsupported command type, just ignore it and fallback to missing command + // we have an unsupported command type + return; } - AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""), - device, groupaddr, endpoint, cluster, cmd, cmd_s); - zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s); ResponseCmndDone(); } - // Parse the "Send" attribute and send the command -void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) { +void ZbSendRead(JsonParserToken val_attr, ZCLMessage & zcl) { // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5} // ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"} // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]} @@ -618,7 +590,7 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) { uint8_t* attrs = nullptr; // empty string is valid size_t attr_item_len = 2; // how many bytes per attribute, standard for "Read" size_t attr_item_offset = 0; // how many bytes do we offset to store attribute - if (ZCL_READ_REPORTING_CONFIGURATION == packet.cmd) { + if (ZCL_READ_REPORTING_CONFIGURATION == zcl.cmd) { attr_item_len = 3; attr_item_offset = 1; } @@ -670,9 +642,9 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) { actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0 found = true; // check cluster - if (0xFFFF == packet.cluster) { - packet.cluster = local_cluster_id; - } else if (packet.cluster != local_cluster_id) { + if (!zcl.validCluster()) { + zcl.cluster = local_cluster_id; + } else if (zcl.cluster != local_cluster_id) { ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); if (attrs) { free(attrs); } return; @@ -688,7 +660,7 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) { attrs_len = actual_attr_len; } else { // value is a literal - if (0xFFFF != packet.cluster) { + if (zcl.validCluster()) { uint16_t val = val_attr.getUInt(); attrs_len = attr_item_len; attrs = (uint8_t*) calloc(attrs_len, 1); @@ -699,10 +671,10 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) { if (attrs_len > 0) { // all good, send the packet - packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr); - packet.msg = attrs; - packet.len = attrs_len; - ZigbeeZCLSend_Raw(packet); + zcl.buf.reserve(attrs_len); + zcl.buf.setLen(0); // clear any previous buffer + zcl.buf.addBuffer(attrs, attrs_len); + zigbeeZCLSendCmd(zcl); ResponseCmndDone(); } else { ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); @@ -741,44 +713,38 @@ void CmndZbSend(void) { if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params - uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid - uint16_t groupaddr = 0x0000; // group address valid only if device == BAD_SHORTADDR - uint16_t cluster = 0xFFFF; // no default - uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint - uint16_t manuf = 0x0000; // Manuf Id in ZCL frame - + ZCLMessage zcl; // prepare the ZCL structure // parse "Device" and "Group" JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)]; if (val_device) { - device = zigbee_devices.parseDeviceFromName(val_device.getStr()).shortaddr; - if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR(D_ZIGBEE_INVALID_PARAM)); return; } + zcl.shortaddr = zigbee_devices.parseDeviceFromName(val_device.getStr()).shortaddr; + if (!zcl.validShortaddr()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_INVALID_PARAM)); return; } } - if (BAD_SHORTADDR == device) { // if not found, check if we have a group + if (!zcl.validShortaddr()) { // if not found, check if we have a group JsonParserToken val_group = root[PSTR(D_CMND_ZIGBEE_GROUP)]; if (val_group) { - groupaddr = val_group.getUInt(); + zcl.groupaddr = val_group.getUInt(); } else { // no device nor group - ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); - return; + ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; } } // from here, either device has a device shortaddr, or if BAD_SHORTADDR then use group address // Note: groupaddr == 0 is valid // read other parameters - cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), cluster); - endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint); - manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), manuf); + zcl.cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), zcl.cluster); + zcl.endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), zcl.endpoint); + zcl.manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), zcl.manuf); // infer endpoint - if (BAD_SHORTADDR == device) { - endpoint = 0xFF; // endpoint not used for group addresses, so use a dummy broadcast endpoint - } else if (0 == endpoint) { // if it was not already specified, try to guess it - endpoint = zigbee_devices.findFirstEndpoint(device); - AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZIG: guessing endpoint %d"), endpoint); + if (!zcl.validShortaddr()) { + zcl.endpoint = 0xFF; // endpoint not used for group addresses, so use a dummy broadcast endpoint + } else if (!zcl.validEndpoint()) { // if it was not already specified, try to guess it + zcl.endpoint = zigbee_devices.findFirstEndpoint(zcl.shortaddr); + AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZIG: guessing endpoint %d"), zcl.endpoint); } - if (0 == endpoint) { // after this, if it is still zero, then it's an error + if (!zcl.validEndpoint()) { // after this, if it is still zero, then it's an error ResponseCmndChar_P(PSTR("Missing endpoint")); return; } @@ -800,30 +766,19 @@ void CmndZbSend(void) { } // from here we have one and only one command - // collate information in a ready to send packet - ZigbeeZCLSendMessage packet({ - device, - groupaddr, - cluster /*cluster*/, - endpoint, - ZCL_READ_ATTRIBUTES, - manuf, /* manuf */ - false /* not cluster specific */, - false /* no response */, - false /* discover route */, - 0, /* zcl transaction id */ - nullptr, 0 - }); + zcl.clusterSpecific = false; /* not cluster specific */ + zcl.needResponse = false; /* no response */ + zcl.direct = false; /* discover route */ if (val_cmd) { // "Send":{...commands...} // we accept either a string or a JSON object - ZbSendSend(val_cmd, device, groupaddr, cluster, endpoint, manuf); + ZbSendSend(val_cmd, zcl); } else if (val_read) { // "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...] // we accept eitehr a number, a string, an array of numbers/strings, or a JSON object - packet.cmd = ZCL_READ_ATTRIBUTES; - ZbSendRead(val_read, packet); + zcl.cmd = ZCL_READ_ATTRIBUTES; + ZbSendRead(val_read, zcl); } else if (val_write) { // only KSON object if (!val_write.isObject()) { @@ -831,8 +786,8 @@ void CmndZbSend(void) { return; } // "Write":{...attributes...} - packet.cmd = ZCL_WRITE_ATTRIBUTES; - ZbSendReportWrite(val_write, packet); + zcl.cmd = ZCL_WRITE_ATTRIBUTES; + ZbSendReportWrite(val_write, zcl); } else if (val_publish) { // "Publish":{...attributes...} // only KSON object @@ -840,8 +795,8 @@ void CmndZbSend(void) { ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); return; } - packet.cmd = ZCL_REPORT_ATTRIBUTES; - ZbSendReportWrite(val_publish, packet); + zcl.cmd = ZCL_REPORT_ATTRIBUTES; + ZbSendReportWrite(val_publish, zcl); } else if (val_response) { // "Report":{...attributes...} // only KSON object @@ -849,13 +804,13 @@ void CmndZbSend(void) { ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); return; } - packet.cmd = ZCL_READ_ATTRIBUTES_RESPONSE; - ZbSendReportWrite(val_response, packet); + zcl.cmd = ZCL_READ_ATTRIBUTES_RESPONSE; + ZbSendReportWrite(val_response, zcl); } else if (val_read_config) { // "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...] // we accept eitehr a number, a string, an array of numbers/strings, or a JSON object - packet.cmd = ZCL_READ_REPORTING_CONFIGURATION; - ZbSendRead(val_read_config, packet); + zcl.cmd = ZCL_READ_REPORTING_CONFIGURATION; + ZbSendRead(val_read_config, zcl); } else if (val_config) { // "Config":{...attributes...} // only JSON object @@ -863,8 +818,8 @@ void CmndZbSend(void) { ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); return; } - packet.cmd = ZCL_CONFIGURE_REPORTING; - ZbSendReportWrite(val_config, packet); + zcl.cmd = ZCL_CONFIGURE_REPORTING; + ZbSendReportWrite(val_config, zcl); } else { Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'")); return;