Zigbee refactor sending ZCL packets

This commit is contained in:
Stephan Hadinger 2021-02-02 20:46:18 +01:00
parent c5cd97064a
commit a67528680e
9 changed files with 422 additions and 493 deletions

View File

@ -52,6 +52,20 @@ public:
delete[] _buf; 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) { inline void setLen(const size_t len) {
uint16_t old_len = _buf->len; uint16_t old_len = _buf->len;
_buf->len = (len <= _buf->size) ? len : _buf->size; _buf->len = (len <= _buf->size) ? len : _buf->size;
@ -118,6 +132,13 @@ public:
return _buf->len; 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) { size_t addBuffer(const SBuffer &buf2) {
if (len() + buf2.len() <= size()) { if (len() + buf2.len() <= size()) {
for (uint32_t i = 0; i < buf2.len(); i++) { for (uint32_t i = 0; i < buf2.len(); i++) {

View File

@ -28,22 +28,40 @@
// //
// structure containing all needed information to send a ZCL packet // structure containing all needed information to send a ZCL packet
// //
class ZigbeeZCLSendMessage { class ZCLMessage {
public: public:
uint16_t shortaddr; ZCLMessage(void); // allocate 16 bytes vy default
uint16_t groupaddr; ZCLMessage(size_t size);
uint16_t cluster;
uint8_t endpoint; inline bool validShortaddr(void) const { return BAD_SHORTADDR != shortaddr; }
uint8_t cmd; inline bool validGroupaddr(void) const { return 0 != groupaddr; }
uint16_t manuf; inline bool validCluster(void) const { return 0xFFFF != cluster; }
bool clusterSpecific; inline bool validEndpoint(void) const { return 0x00 != endpoint; }
bool needResponse; inline bool validCmd(void) const { return 0xFF != cmd; }
bool direct; // true if direct, false if discover router
uint8_t transacId; // ZCL transaction number inline void setTransac(uint8_t _transac) { transac = _transac; transacSet = true; }
const uint8_t *msg;
size_t len; 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_Func)(uint8_t value);
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const SBuffer &buf); typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const SBuffer &buf);
@ -119,8 +137,8 @@ public:
struct ZigbeeStatus zigbee; struct ZigbeeStatus zigbee;
SBuffer *zigbee_buffer = nullptr; SBuffer *zigbee_buffer = nullptr;
void zigbeeZCLSendCmd(const ZigbeeZCLSendMessage &msg); void zigbeeZCLSendCmd(ZCLMessage &msg);
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl); void ZigbeeZCLSend_Raw(const ZCLMessage &zcl);
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false); bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false);
// parse Hex formatted attribute names like '0301/0001" // parse Hex formatted attribute names like '0301/0001"

View File

@ -152,27 +152,21 @@ void ZigbeeHueGroups(String * lights) {
} }
void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) { void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) {
zigbeeZCLSendCmd(ZigbeeZCLSendMessage({ ZCLMessage zcl(&s ? s.len() : 0);
shortaddr, zcl.shortaddr = shortaddr;
0 /* groupaddr */, zcl.cluster = cluster;
cluster /*cluster*/, zcl.cmd = cmd;
0 /* endpoint */, zcl.clusterSpecific = true;
cmd /* cmd */, zcl.needResponse = true;
0, /* manuf */ zcl.direct = false; // discover route
true /* cluster specific */, if (&s) { zcl.buf.replace(s); }
true /* response */, zigbeeZCLSendCmd(zcl);
false /* discover route */,
0, /* zcl transaction id */
(&s != nullptr) ? s.getBuffer() : nullptr,
(&s != nullptr) ? s.len() : 0
}));
} }
// Send commands // Send commands
// Power On/Off // Power On/Off
void ZigbeeHuePower(uint16_t shortaddr, bool power) { void ZigbeeHuePower(uint16_t shortaddr, bool power) {
ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, *(SBuffer*)nullptr); 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); zigbee_devices.getShortAddr(shortaddr).setPower(power, 0);
} }
@ -183,23 +177,16 @@ void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) {
s.add8(dimmer); s.add8(dimmer);
s.add16(0x000A); // transition time = 1s s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0008, 0x04, s); 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); zigbee_devices.getLight(shortaddr).setDimmer(dimmer);
} }
// CT // CT
void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) { void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) {
if (ct > 0xFEFF) { ct = 0xFEFF; } if (ct > 0xFEFF) { ct = 0xFEFF; }
// AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct);
SBuffer s(4); SBuffer s(4);
s.add16(ct); s.add16(ct);
s.add16(0x000A); // transition time = 1s s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s); 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); Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(2); // "ct" light.setColorMode(2); // "ct"
light.setCT(ct); light.setCT(ct);
@ -214,10 +201,6 @@ void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) {
s.add16(y); s.add16(y);
s.add16(0x000A); // transition time = 1s s.add16(0x000A); // transition time = 1s
ZigbeeSendHue(shortaddr, 0x0300, 0x07, s); 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); Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(1); // "xy" light.setColorMode(1); // "xy"
light.setX(x); light.setX(x);
@ -233,10 +216,6 @@ void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) {
s.add8(sat); s.add8(sat);
s.add16(0); s.add16(0);
ZigbeeSendHue(shortaddr, 0x0300, 0x06, s); 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); Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
light.setColorMode(0); // "hs" light.setColorMode(0); // "hs"
light.setSat(sat); light.setSat(sat);

View File

@ -381,7 +381,6 @@ const char Z_strings[] PROGMEM =
"ProductURL" "\x00" "ProductURL" "\x00"
"QualityMeasure" "\x00" "QualityMeasure" "\x00"
"RGB" "\x00" "RGB" "\x00"
"RGBb" "\x00"
"RMSCurrent" "\x00" "RMSCurrent" "\x00"
"RMSVoltage" "\x00" "RMSVoltage" "\x00"
"ReactivePower" "\x00" "ReactivePower" "\x00"
@ -474,6 +473,7 @@ const char Z_strings[] PROGMEM =
"ZoneStatus" "\x00" "ZoneStatus" "\x00"
"ZoneStatusChange" "\x00" "ZoneStatusChange" "\x00"
"ZoneType" "\x00" "ZoneType" "\x00"
"_" "\x00"
"xx" "\x00" "xx" "\x00"
"xx000A00" "\x00" "xx000A00" "\x00"
"xx0A" "\x00" "xx0A" "\x00"
@ -805,121 +805,121 @@ enum Z_offsets {
Zo_ProductURL = 4956, Zo_ProductURL = 4956,
Zo_QualityMeasure = 4967, Zo_QualityMeasure = 4967,
Zo_RGB = 4982, Zo_RGB = 4982,
Zo_RGBb = 4986, Zo_RMSCurrent = 4986,
Zo_RMSCurrent = 4991, Zo_RMSVoltage = 4997,
Zo_RMSVoltage = 5002, Zo_ReactivePower = 5008,
Zo_ReactivePower = 5013, Zo_RecallScene = 5022,
Zo_RecallScene = 5027, Zo_RemainingTime = 5034,
Zo_RemainingTime = 5039, Zo_RemoteSensing = 5048,
Zo_RemoteSensing = 5053, Zo_RemoveAllGroups = 5062,
Zo_RemoveAllGroups = 5067, Zo_RemoveAllScenes = 5078,
Zo_RemoveAllScenes = 5083, Zo_RemoveGroup = 5094,
Zo_RemoveGroup = 5099, Zo_RemoveScene = 5106,
Zo_RemoveScene = 5111, Zo_ResetAlarm = 5118,
Zo_ResetAlarm = 5123, Zo_ResetAllAlarms = 5129,
Zo_ResetAllAlarms = 5134, Zo_SWBuildID = 5144,
Zo_SWBuildID = 5149, Zo_Sat = 5154,
Zo_Sat = 5159, Zo_SatMove = 5158,
Zo_SatMove = 5163, Zo_SatStep = 5166,
Zo_SatStep = 5171, Zo_SceneCount = 5174,
Zo_SceneCount = 5179, Zo_SceneValid = 5185,
Zo_SceneValid = 5190, Zo_ScheduleMode = 5196,
Zo_ScheduleMode = 5201, Zo_SeaPressure = 5209,
Zo_SeaPressure = 5214, Zo_ShortPollInterval = 5221,
Zo_ShortPollInterval = 5226, Zo_Shutter = 5239,
Zo_Shutter = 5244, Zo_ShutterClose = 5247,
Zo_ShutterClose = 5252, Zo_ShutterLift = 5260,
Zo_ShutterLift = 5265, Zo_ShutterOpen = 5272,
Zo_ShutterOpen = 5277, Zo_ShutterStop = 5284,
Zo_ShutterStop = 5289, Zo_ShutterTilt = 5296,
Zo_ShutterTilt = 5301, Zo_SoftwareRevision = 5308,
Zo_SoftwareRevision = 5313, Zo_StackVersion = 5325,
Zo_StackVersion = 5330, Zo_StandardTime = 5338,
Zo_StandardTime = 5343, Zo_StartUpOnOff = 5351,
Zo_StartUpOnOff = 5356, Zo_Status = 5364,
Zo_Status = 5369, Zo_StoreScene = 5371,
Zo_StoreScene = 5376, Zo_SwitchType = 5382,
Zo_SwitchType = 5387, Zo_SystemMode = 5393,
Zo_SystemMode = 5398, Zo_TRVBoost = 5404,
Zo_TRVBoost = 5409, Zo_TRVChildProtection = 5413,
Zo_TRVChildProtection = 5418, Zo_TRVMirrorDisplay = 5432,
Zo_TRVMirrorDisplay = 5437, Zo_TRVMode = 5449,
Zo_TRVMode = 5454, Zo_TRVWindowOpen = 5457,
Zo_TRVWindowOpen = 5462, Zo_TempTarget = 5471,
Zo_TempTarget = 5476, Zo_Temperature = 5482,
Zo_Temperature = 5487, Zo_TemperatureMaxMeasuredValue = 5494,
Zo_TemperatureMaxMeasuredValue = 5499, Zo_TemperatureMinMeasuredValue = 5522,
Zo_TemperatureMinMeasuredValue = 5527, Zo_TemperatureTolerance = 5550,
Zo_TemperatureTolerance = 5555, Zo_TerncyDuration = 5571,
Zo_TerncyDuration = 5576, Zo_TerncyRotate = 5586,
Zo_TerncyRotate = 5591, Zo_ThSetpoint = 5599,
Zo_ThSetpoint = 5604, Zo_Time = 5610,
Zo_Time = 5615, Zo_TimeEpoch = 5615,
Zo_TimeEpoch = 5620, Zo_TimeStatus = 5625,
Zo_TimeStatus = 5630, Zo_TimeZone = 5636,
Zo_TimeZone = 5641, Zo_TotalProfileNum = 5645,
Zo_TotalProfileNum = 5650, Zo_TuyaAutoLock = 5661,
Zo_TuyaAutoLock = 5666, Zo_TuyaAwayDays = 5674,
Zo_TuyaAwayDays = 5679, Zo_TuyaAwayTemp = 5687,
Zo_TuyaAwayTemp = 5692, Zo_TuyaBattery = 5700,
Zo_TuyaBattery = 5705, Zo_TuyaBoostTime = 5712,
Zo_TuyaBoostTime = 5717, Zo_TuyaChildLock = 5726,
Zo_TuyaChildLock = 5731, Zo_TuyaComfortTemp = 5740,
Zo_TuyaComfortTemp = 5745, Zo_TuyaEcoTemp = 5756,
Zo_TuyaEcoTemp = 5761, Zo_TuyaFanMode = 5768,
Zo_TuyaFanMode = 5773, Zo_TuyaForceMode = 5780,
Zo_TuyaForceMode = 5785, Zo_TuyaMaxTemp = 5794,
Zo_TuyaMaxTemp = 5799, Zo_TuyaMinTemp = 5806,
Zo_TuyaMinTemp = 5811, Zo_TuyaPreset = 5818,
Zo_TuyaPreset = 5823, Zo_TuyaScheduleHolidays = 5829,
Zo_TuyaScheduleHolidays = 5834, Zo_TuyaScheduleWorkdays = 5850,
Zo_TuyaScheduleWorkdays = 5855, Zo_TuyaTempTarget = 5871,
Zo_TuyaTempTarget = 5876, Zo_TuyaValveDetection = 5886,
Zo_TuyaValveDetection = 5891, Zo_TuyaValvePosition = 5905,
Zo_TuyaValvePosition = 5910, Zo_TuyaWeekSelect = 5923,
Zo_TuyaWeekSelect = 5928, Zo_TuyaWindowDetection = 5938,
Zo_TuyaWindowDetection = 5943, Zo_UnoccupiedCoolingSetpoint = 5958,
Zo_UnoccupiedCoolingSetpoint = 5963, Zo_UnoccupiedHeatingSetpoint = 5984,
Zo_UnoccupiedHeatingSetpoint = 5989, Zo_UtilityName = 6010,
Zo_UtilityName = 6015, Zo_ValidUntilTime = 6022,
Zo_ValidUntilTime = 6027, Zo_ValvePosition = 6037,
Zo_ValvePosition = 6042, Zo_VelocityLift = 6051,
Zo_VelocityLift = 6056, Zo_ViewGroup = 6064,
Zo_ViewGroup = 6069, Zo_ViewScene = 6074,
Zo_ViewScene = 6079, Zo_Water = 6084,
Zo_Water = 6089, Zo_WhitePointX = 6090,
Zo_WhitePointX = 6095, Zo_WhitePointY = 6102,
Zo_WhitePointY = 6107, Zo_WindowCoveringType = 6114,
Zo_WindowCoveringType = 6119, Zo_X = 6133,
Zo_X = 6138, Zo_Y = 6135,
Zo_Y = 6140, Zo_ZCLVersion = 6137,
Zo_ZCLVersion = 6142, Zo_ZoneState = 6148,
Zo_ZoneState = 6153, Zo_ZoneStatus = 6158,
Zo_ZoneStatus = 6163, Zo_ZoneStatusChange = 6169,
Zo_ZoneStatusChange = 6174, Zo_ZoneType = 6186,
Zo_ZoneType = 6191, Zo__ = 6195,
Zo_xx = 6200, Zo_xx = 6197,
Zo_xx000A00 = 6203, Zo_xx000A00 = 6200,
Zo_xx0A = 6212, Zo_xx0A = 6209,
Zo_xx0A00 = 6217, Zo_xx0A00 = 6214,
Zo_xx19 = 6224, Zo_xx19 = 6221,
Zo_xx190A = 6229, Zo_xx190A = 6226,
Zo_xx190A00 = 6236, Zo_xx190A00 = 6233,
Zo_xxxx = 6245, Zo_xxxx = 6242,
Zo_xxxx00 = 6250, Zo_xxxx00 = 6247,
Zo_xxxx0A00 = 6257, Zo_xxxx0A00 = 6254,
Zo_xxxxyy = 6266, Zo_xxxxyy = 6263,
Zo_xxxxyyyy = 6273, Zo_xxxxyyyy = 6270,
Zo_xxxxyyyy0A00 = 6282, Zo_xxxxyyyy0A00 = 6279,
Zo_xxxxyyzz = 6295, Zo_xxxxyyzz = 6292,
Zo_xxyy = 6304, Zo_xxyy = 6301,
Zo_xxyy0A00 = 6309, Zo_xxyy0A00 = 6306,
Zo_xxyyyy = 6318, Zo_xxyyyy = 6315,
Zo_xxyyyy000000000000 = 6325, Zo_xxyyyy000000000000 = 6322,
Zo_xxyyyy0A0000000000 = 6344, Zo_xxyyyy0A0000000000 = 6341,
Zo_xxyyyyzz = 6363, Zo_xxyyyyzz = 6360,
Zo_xxyyyyzzzz = 6372, Zo_xxyyyyzzzz = 6369,
Zo_xxyyzzzz = 6383, Zo_xxyyzzzz = 6380,
}; };

View File

@ -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 // The sensor expects the coordinator to send a Default Response to acknowledge the attribute reporting
if (0 == _frame_control.b.disable_def_resp) { if (0 == _frame_control.b.disable_def_resp) {
// the device expects a default response // the device expects a default response
SBuffer buf(2); ZCLMessage zcl(2); // message is 2 bytes
buf.add8(_cmd_id); zcl.shortaddr = _srcaddr;
buf.add8(0x00); // Status = OK zcl.cluster = _cluster_id;
zcl.endpoint = _srcendpoint;
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ zcl.cmd = ZCL_DEFAULT_RESPONSE;
_srcaddr, zcl.manuf = _manuf_code;
0x0000, zcl.clusterSpecific = false; /* not cluster specific */
_cluster_id, zcl.needResponse = false; /* noresponse */
_srcendpoint, zcl.direct = true; /* direct no retry */
ZCL_DEFAULT_RESPONSE, zcl.setTransac(_transact_seq);
_manuf_code, zcl.buf.add8(_cmd_id);
false /* not cluster specific */, zcl.buf.add8(0); // Status = OK
false /* noresponse */, zigbeeZCLSendCmd(zcl);
true /* direct no retry */,
_transact_seq, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
} }
} }
@ -1674,23 +1670,19 @@ void ZCLFrame::parseClusterSpecificCommand(Z_attribute_list& attr_list) {
// Send Default Response to acknowledge the attribute reporting // Send Default Response to acknowledge the attribute reporting
if (0 == _frame_control.b.disable_def_resp) { if (0 == _frame_control.b.disable_def_resp) {
// the device expects a default response // the device expects a default response
SBuffer buf(2); ZCLMessage zcl(2); // message is 4 bytes
buf.add8(_cmd_id); zcl.shortaddr = _srcaddr;
buf.add8(0x00); // Status = OK zcl.cluster = _cluster_id;
zcl.endpoint = _srcendpoint;
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ zcl.cmd = ZCL_DEFAULT_RESPONSE;
_srcaddr, zcl.manuf = _manuf_code;
0x0000, zcl.clusterSpecific = false; /* not cluster specific */
_cluster_id, zcl.needResponse = false; /* noresponse */
_srcendpoint, zcl.direct = true; /* direct no retry */
ZCL_DEFAULT_RESPONSE, zcl.setTransac(_transact_seq);
_manuf_code, zcl.buf.add8(_cmd_id);
false /* not cluster specific */, zcl.buf.add8(0x00); // Status = OK
false /* noresponse */, zigbeeZCLSendCmd(zcl);
true /* direct no retry */,
_transact_seq, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
} }
} }

View File

@ -46,7 +46,7 @@ typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z
// - cluster: cluster number of the command // - cluster: cluster number of the command
// - cmd: the command number, of 0xFF if it's actually a variable to be assigned from 'xx' // - 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 // - 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 = { const Z_CommandConverter Z_Commands[] PROGMEM = {
// Identify cluster // Identify cluster
{ Z_(Identify), 0x0003, 0x00, 0x01, Z_(xxxx) }, // Identify device, time in seconds { 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_(HueSat), 0x0300, 0x06, 0x01, Z_(xxyy0A00) }, // Hue, Sat
{ Z_(Color), 0x0300, 0x07, 0x01, Z_(xxxxyyyy0A00) }, // x, y (uint16) { Z_(Color), 0x0300, 0x07, 0x01, Z_(xxxxyyyy0A00) }, // x, y (uint16)
{ Z_(CT), 0x0300, 0x0A, 0x01, Z_(xxxx0A00) }, // Color Temperature Mireds (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_(ShutterOpen), 0x0102, 0x00, 0x01, Z_() },
{ Z_(ShutterClose), 0x0102, 0x01, 0x01, Z_() }, { Z_(ShutterClose), 0x0102, 0x01, 0x01, Z_() },
{ Z_(ShutterStop), 0x0102, 0x02, 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) { if (groupaddr) {
shortaddr = BAD_SHORTADDR; // if group address, don't send to device shortaddr = BAD_SHORTADDR; // if group address, don't send to device
} }
uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ ZCLMessage zcl(attrs_len); // message is `attrs_len` bytes
shortaddr, zcl.shortaddr = shortaddr;
groupaddr, zcl.groupaddr = groupaddr;
cluster /*cluster*/, zcl.cluster = cluster;
endpoint, zcl.endpoint = endpoint;
ZCL_READ_ATTRIBUTES, zcl.cmd = ZCL_READ_ATTRIBUTES;
0, /* manuf */ zcl.clusterSpecific = false;
false /* not cluster specific */, zcl.needResponse = true;
true /* response */, zcl.direct = false; // discover route
false /* discover route */, zcl.buf.addBuffer(attrs, attrs_len);
seq, /* zcl transaction id */ zigbeeZCLSendCmd(zcl);
attrs, attrs_len
}));
} }
} }
@ -535,17 +533,19 @@ bool convertTuyaSpecificCluster(class Z_attribute_list &attr_list, uint16_t clus
// Only take commands outgoing, i.e. direction == 0 // Only take commands outgoing, i.e. direction == 0
// If not found: // If not found:
// - returns nullptr // - 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; } if (nullptr == command) { return nullptr; }
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) { for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i]; const Z_CommandConverter *conv = &Z_Commands[i];
uint8_t conv_direction = pgm_read_byte(&conv->direction); uint8_t conv_direction = pgm_read_byte(&conv->direction);
uint8_t conv_cmd = pgm_read_byte(&conv->cmd); uint8_t conv_cmd = pgm_read_byte(&conv->cmd);
uint16_t conv_cluster = pgm_read_word(&conv->cluster); 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)))) { if ((conv_direction & 0x01) && (0 == strcasecmp_P(command, Z_strings + pgm_read_word(&conv->tasmota_cmd_offset)))) {
*cluster = conv_cluster; *cluster = conv_cluster;
*cmd = conv_cmd; *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) // 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) { // Returns a SBuffer allocated object, it is the caller's responsibility to delete it
size_t len = strlen_P(zcl_cmd_P); void zigbeeCmdAddParams(SBuffer & buf, const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) {
char zcl_cmd[len+1]; size_t hex_len = strlen_P(zcl_cmd_P);
strcpy_P(zcl_cmd, zcl_cmd_P); // copy into RAM buf.reserve((hex_len + 1)/2);
char *p = zcl_cmd; const char * p = zcl_cmd_P;
while (*p) { char c0, c1;
if (isXYZ(*p) && (*p == *(p+1))) { // if char is [x-z] and followed by same char 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; uint8_t val = 0;
switch (*p) { switch (pgm_read_byte(p)) {
case 'x': case 'x':
val = x & 0xFF; val = x & 0xFF;
x = x >> 8; x = x >> 8;
@ -582,15 +583,18 @@ String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_
z = z >> 8; z = z >> 8;
break; break;
} }
*p = hexDigit(val >> 4); buf.add8(val);
*(p+1) = hexDigit(val); // *p = hexDigit(val >> 4);
p++; // *(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 const char kZ_Alias[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" "OPEN" "|" // 0

View File

@ -1319,78 +1319,55 @@ void Z_SendSimpleDescReq(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluste
// //
// Send AF Info Request // Send AF Info Request
// Queue requests for the device // 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: // 2. Auto-bind to coordinator:
// Iterate among // Iterate among
// //
void Z_SendDeviceInfoRequest(uint16_t shortaddr) { void Z_SendDeviceInfoRequest(uint16_t shortaddr) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr); ZCLMessage zcl(4); // message is 4 bytes
if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01 zcl.shortaddr = shortaddr;
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr); zcl.cluster = 0;
zcl.cmd = ZCL_READ_ATTRIBUTES;
uint8_t InfoReq[] = { 0x04, 0x00, 0x05, 0x00 }; zcl.clusterSpecific = false;
zcl.needResponse = true;
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ zcl.direct = false; // discover route
shortaddr, zcl.buf.add16(0x0005);
0x0000, /* group */ zcl.buf.add16(0x0004);
0x0000 /*cluster*/, zigbeeZCLSendCmd(zcl);
endpoint,
ZCL_READ_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
false /* discover route */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
} }
// //
// Send single attribute read request in Timer // 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) { 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); ZCLMessage zcl(2); // message is 2 bytes
uint8_t InfoReq[2] = { Z_B0(value), Z_B1(value) }; // list of single attribute zcl.shortaddr = shortaddr;
zcl.cluster = cluster;
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ zcl.endpoint = endpoint;
shortaddr, zcl.cmd = ZCL_READ_ATTRIBUTES;
0x0000, /* group */ zcl.clusterSpecific = false;
cluster /*cluster*/, zcl.needResponse = true;
endpoint, zcl.direct = false; // discover route
ZCL_READ_ATTRIBUTES, zcl.buf.add16(value); // 04000500
0x0000, /* manuf */ zigbeeZCLSendCmd(zcl);
false /* not cluster specific */,
true /* response */,
false /* discover route */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
} }
// //
// Write CIE address // Write CIE address
// //
void Z_WriteCIEAddress(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { 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); ZCLMessage zcl(12); // message is 12 bytes
SBuffer buf(12); zcl.shortaddr = shortaddr;
buf.add16(0x0010); // attribute 0x0010 zcl.cluster = 0x0500;
buf.add8(ZEUI64); zcl.endpoint = endpoint;
buf.add64(localIEEEAddr); zcl.cmd = ZCL_WRITE_ATTRIBUTES;
zcl.clusterSpecific = false;
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Writing CIE address")); zcl.needResponse = true;
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ zcl.direct = false; // discover route
shortaddr, zcl.buf.add16(0x0010); // attribute 0x0010
0x0000, /* group */ zcl.buf.add8(ZEUI64);
0x0500 /*cluster*/, zcl.buf.add64(localIEEEAddr);
endpoint, zigbeeZCLSendCmd(zcl);
ZCL_WRITE_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
false /* discover route */,
transacid, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
} }
@ -1398,23 +1375,18 @@ void Z_WriteCIEAddress(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster,
// Write CIE address // Write CIE address
// //
void Z_SendCIEZoneEnrollResponse(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { 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)); AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Sending Enroll Zone %d"), Z_B0(value));
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ ZCLMessage zcl(2); // message is 2 bytes
shortaddr, zcl.shortaddr = shortaddr;
0x0000, /* group */ zcl.cluster = 0x0500;
0x0500 /*cluster*/, zcl.endpoint = endpoint;
endpoint, zcl.cmd = 0x00, // Zone Enroll Response
0x00, // Zone Enroll Response zcl.clusterSpecific = true;
0x0000, /* manuf */ zcl.needResponse = true;
true /* cluster specific */, zcl.direct = false; // discover route
true /* response */, zcl.buf.add8(0x00); // success
false /* discover route */, zcl.buf.add8(Z_B0(value)); // ZoneID
transacid, /* zcl transaction id */ zigbeeZCLSendCmd(zcl);
EnrollRSP, sizeof(EnrollRSP)
}));
} }
// //
@ -1549,19 +1521,16 @@ void Z_AutoConfigReportingForCluster(uint16_t shortaddr, uint16_t groupaddr, uin
if (buf.len() > 0) { if (buf.len() > 0) {
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `%s`"), TasmotaGlobal.mqtt_data); AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `%s`"), TasmotaGlobal.mqtt_data);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ ZCLMessage zcl(buf.len()); // message is 4 bytes
shortaddr, zcl.shortaddr = shortaddr;
0x0000, /* group */ zcl.cluster = cluster;
cluster /*cluster*/, zcl.endpoint = endpoint;
endpoint, zcl.cmd = ZCL_CONFIGURE_REPORTING;
ZCL_CONFIGURE_REPORTING, zcl.clusterSpecific = false; /* not cluster specific */
0x0000, /* manuf */ zcl.needResponse = false; /* noresponse */
false /* not cluster specific */, zcl.direct = false; /* discover route */
false /* no response */, zcl.buf.addBuffer(buf);
false /* discover route */, zigbeeZCLSendCmd(zcl);
zigbee_devices.getNextSeqNumber(shortaddr), /* zcl transaction id */
buf.buf(), buf.len()
}));
} }
} }
@ -2149,19 +2118,17 @@ void ZCLFrame::autoResponder(const uint16_t *attr_list_ids, size_t attr_len) {
// send // send
// all good, send the packet // all good, send the packet
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ ZCLMessage zcl(buf.len()); // message is 4 bytes
_srcaddr, zcl.shortaddr = _srcaddr;
0x0000, zcl.cluster = _cluster_id;
_cluster_id /*cluster*/, zcl.endpoint = _srcendpoint;
_srcendpoint, zcl.cmd = ZCL_READ_ATTRIBUTES_RESPONSE;
ZCL_READ_ATTRIBUTES_RESPONSE, zcl.clusterSpecific = false; /* not cluster specific */
0x0000, /* manuf */ zcl.needResponse = false; /* noresponse */
false /* not cluster specific */, zcl.direct = true; /* direct response */
false /* no response */, zcl.setTransac(_transact_seq);
true /* direct response */, zcl.buf.addBuffer(buf);
_transact_seq, /* zcl transaction id */ zigbeeZCLSendCmd(zcl);
buf.getBuffer(), buf.len()
}));
} }
} }

View File

@ -748,16 +748,17 @@ void CmndZbEZSPSend(void)
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr // - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
// - len: length of the 'msg' payload // - len: length of the 'msg' payload
// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond // - 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 // Returns: None
// //
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
void ZigbeeZCLSend_Raw(const ZCLMessage &zcl) {
SBuffer buf(32+zcl.buf.len());
#ifdef USE_ZIGBEE_ZNP #ifdef USE_ZIGBEE_ZNP
SBuffer buf(32+zcl.len);
buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02 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.add8(Z_Addr_Group); // 01
buf.add64(zcl.groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded 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 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.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
buf.add8(0x01); // source endpoint buf.add8(0x01); // source endpoint
buf.add16(zcl.cluster); buf.add16(zcl.cluster);
buf.add8(zcl.transacId); // transacId buf.add8(zcl.transac); // transac
buf.add8(0x30); // 30 options buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius 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 buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) { if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null 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); buf.add8(zcl.cmd);
if (zcl.len > 0) { buf.addBuffer(zcl.buf);
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
ZigbeeZNPSend(buf.getBuffer(), buf.len()); ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP #endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP #ifdef USE_ZIGBEE_EZSP
SBuffer buf(32+zcl.len); if (zcl.validShortaddr()) {
if (BAD_SHORTADDR != zcl.shortaddr) {
// send unicast message to an address // send unicast message to an address
buf.add16(EZSP_sendUnicast); // 3400 buf.add16(EZSP_sendUnicast); // 3400
buf.add8(EMBER_OUTGOING_DIRECT); // 00 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(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
} }
buf.add16(zcl.groupaddr); // groupId buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transacId); buf.add8(zcl.transac);
// end of ApsFrame // end of ApsFrame
buf.add8(0x01); // tag TODO 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 buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) { if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null 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); buf.add8(zcl.cmd);
if (zcl.len > 0) { buf.addBuffer(zcl.buf);
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
} else { } else {
// send broadcast group address, aka groupcast // send broadcast group address, aka groupcast
buf.add16(EZSP_sendMulticast); // 3800 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(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
} }
buf.add16(zcl.groupaddr); // groupId buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transacId); buf.add8(zcl.transac);
// end of ApsFrame // end of ApsFrame
buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS
buf.add8(7); // nonMemberRadius, 7 = infinite buf.add8(7); // nonMemberRadius, 7 = infinite
buf.add8(0x01); // tag TODO 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 buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) { if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null 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); buf.add8(zcl.cmd);
if (zcl.len > 0) { buf.addBuffer(zcl.buf);
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
} }
ZigbeeEZSPSendCmd(buf.buf(), buf.len()); ZigbeeEZSPSendCmd(buf.buf(), buf.len());

View File

@ -179,59 +179,39 @@ void CmndZbReset(void) {
// - param: pointer to HEX string for payload, should not be nullptr // - param: pointer to HEX string for payload, should not be nullptr
// Returns: None // Returns: None
// //
void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, bool clusterSpecific, uint16_t manuf, void zigbeeZCLSendCmd(class ZCLMessage &zcl) {
uint16_t cluster, uint8_t cmd, const char *param) { if ((0 == zcl.endpoint) && (zcl.validShortaddr())) {
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(&param, 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)) {
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address // 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: 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"), // AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %_B"),
msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint, msg.cmd, msg.len, msg.msg); // 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")); AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return; return;
} }
// everything is good, we can send the command // everything is good, we can send the command
msg.transacId = zigbee_devices.getNextSeqNumber(msg.shortaddr); if (!zcl.transacSet) {
ZigbeeZCLSend_Raw(msg); 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 // now set the timer, if any, to read back the state later
if (msg.clusterSpecific) { if (zcl.clusterSpecific) {
if (!Settings.flag5.zb_disable_autoquery) { if (!Settings.flag5.zb_disable_autoquery) {
// read back attribute value unless it is disabled // 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 // 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) // 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) { void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLMessage & zcl) {
SBuffer buf(200); // buffer to store the binary output of attibutes zcl.buf.reserve(200); // buffer to store the binary output of attibutes
SBuffer & buf = zcl.buf; // synonym
if (nullptr == XdrvMailbox.command) { if (nullptr == XdrvMailbox.command) {
XdrvMailbox.command = (char*) ""; // prevent a crash when calling ReponseCmndChar and there was no previous 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 // iterate on keys
for (auto key : val_pubwrite.getObject()) { for (auto key : val_pubwrite.getObject()) {
@ -361,9 +342,9 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
// Buffer ready, do some sanity checks // Buffer ready, do some sanity checks
// all attributes must use the same cluster // all attributes must use the same cluster
if (0xFFFF == packet.cluster) { if (0xFFFF == zcl.cluster) {
packet.cluster = attr.key.id.cluster; // set the cluster for this packet zcl.cluster = attr.key.id.cluster; // set the cluster for this packet
} else if (packet.cluster != attr.key.id.cluster) { } else if (zcl.cluster != attr.key.id.cluster) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
return; return;
} }
@ -390,20 +371,20 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
const char* val_str = ""; // variant as string const char* val_str = ""; // variant as string
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Split encoding depending on message // Split encoding depending on message
if (packet.cmd != ZCL_CONFIGURE_REPORTING) { if (zcl.cmd != ZCL_CONFIGURE_REPORTING) {
if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) { if ((zcl.cluster == 0XEF00) && (zcl.cmd == ZCL_WRITE_ATTRIBUTES)) {
// special case of Tuya / Moes / Lidl attributes // special case of Tuya / Moes / Lidl attributes
if (buf.len() == 0) { if (buf.len() == 0) {
// add the prefix to data // add the prefix to data
buf.add8(0); // status buf.add8(0); // status
buf.add8(zigbee_devices.getNextSeqNumber(packet.shortaddr)); buf.add8(zigbee_devices.getNextSeqNumber(zcl.shortaddr));
} }
packet.clusterSpecific = true; zcl.clusterSpecific = true;
packet.cmd = 0x00; zcl.cmd = 0x00;
if (!ZbTuyaWrite(buf, attr)) { if (!ZbTuyaWrite(buf, attr)) {
return; // error 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 return; // error
} }
} else { } else {
@ -464,19 +445,13 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
} }
// all good, send the packet // all good, send the packet
packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr); zigbeeZCLSendCmd(zcl);
packet.msg = buf.getBuffer();
packet.len = buf.len();
ZigbeeZCLSend_Raw(packet);
ResponseCmndDone(); ResponseCmndDone();
} }
// Parse the "Send" attribute and send the command // 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) { void ZbSendSend(class JsonParserToken val_cmd, ZCLMessage & zcl) {
uint8_t cmd = 0; zcl.clusterSpecific = true;
String cmd_str = ""; // the actual low-level command, either specified or computed
const char *cmd_s = ""; // pointer to payload string
bool clusterSpecific = true;
static const char delim[] = ", "; // delimiters for parameters static const char delim[] = ", "; // delimiters for parameters
// probe the type of the argument // 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 cmd_var;
uint16_t local_cluster_id; uint16_t local_cluster_id;
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var); const char * tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var);
if (tasmota_cmd) { if (tasmota_cmd == nullptr) { // did we find the command?
cmd_str = tasmota_cmd;
} else {
Response_P(PSTR(D_ZIGBEE_UNRECOGNIZED_COMMAND), key.getStr()); Response_P(PSTR(D_ZIGBEE_UNRECOGNIZED_COMMAND), key.getStr());
return; return;
} }
// check cluster // check cluster
if (0xFFFF == cluster) { if (0xFFFF == zcl.cluster) {
cluster = local_cluster_id; zcl.cluster = local_cluster_id;
} else if (cluster != local_cluster_id) { } else if (zcl.cluster != local_cluster_id) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
return; 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()); //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 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 x = y; // and shift other variables
y = z; y = z;
} else { } 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 zigbeeCmdAddParams(zcl.buf, tasmota_cmd, 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();
} else { } else {
// we have zero command, pass through until last error for missing command // we have zero command, pass through until last error for missing command
return;
} }
zigbeeZCLSendCmd(zcl);
} else if (val_cmd.isStr()) { } else if (val_cmd.isStr()) {
// low-level command // low-level command
// Now parse the string to extract cluster, command, and payload // Now parse the string to extract cluster, command, and payload
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC" // 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 // First delimiter is '_' for a global command, or '!' for a cluster specific command
const char * data = val_cmd.getStr(); const char * data = val_cmd.getStr();
uint16_t local_cluster_id = parseHex(&data, 4); uint16_t local_cluster_id = parseHex(&data, 4);
// check cluster // check cluster
if (0xFFFF == cluster) { if (0xFFFF == zcl.cluster) {
cluster = local_cluster_id; zcl.cluster = local_cluster_id;
} else if (cluster != local_cluster_id) { } else if (zcl.cluster != local_cluster_id) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
return; return;
} }
// delimiter // delimiter
if (('_' == *data) || ('!' == *data)) { if (('_' == *data) || ('!' == *data)) {
if ('_' == *data) { clusterSpecific = false; } if ('_' == *data) { zcl.clusterSpecific = false; }
data++; data++;
} else { } else {
ResponseCmndChar_P(PSTR(D_ZIGBEE_WRONG_DELIMITER)); ResponseCmndChar_P(PSTR(D_ZIGBEE_WRONG_DELIMITER));
return; return;
} }
// parse cmd number // parse cmd number
cmd = parseHex(&data, 2); zcl.cmd = parseHex(&data, 2);
// move to end of payload // move to end of payload
// delimiter is optional // delimiter is optional
if ('/' == *data) { data++; } // skip delimiter if ('/' == *data) { data++; } // skip delimiter
cmd_s = data; zcl.buf.replace(SBuffer::SBufferFromHex(data, strlen(data)));
zigbeeZCLSendCmd(zcl);
} else { } 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(); ResponseCmndDone();
} }
// Parse the "Send" attribute and send the command // 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":0,"Endpoint":3,"Read":5}
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"} // ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]} // 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 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_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 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_len = 3;
attr_item_offset = 1; 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 actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
found = true; found = true;
// check cluster // check cluster
if (0xFFFF == packet.cluster) { if (!zcl.validCluster()) {
packet.cluster = local_cluster_id; zcl.cluster = local_cluster_id;
} else if (packet.cluster != local_cluster_id) { } else if (zcl.cluster != local_cluster_id) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
if (attrs) { free(attrs); } if (attrs) { free(attrs); }
return; return;
@ -688,7 +660,7 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) {
attrs_len = actual_attr_len; attrs_len = actual_attr_len;
} else { } else {
// value is a literal // value is a literal
if (0xFFFF != packet.cluster) { if (zcl.validCluster()) {
uint16_t val = val_attr.getUInt(); uint16_t val = val_attr.getUInt();
attrs_len = attr_item_len; attrs_len = attr_item_len;
attrs = (uint8_t*) calloc(attrs_len, 1); attrs = (uint8_t*) calloc(attrs_len, 1);
@ -699,10 +671,10 @@ void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) {
if (attrs_len > 0) { if (attrs_len > 0) {
// all good, send the packet // all good, send the packet
packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr); zcl.buf.reserve(attrs_len);
packet.msg = attrs; zcl.buf.setLen(0); // clear any previous buffer
packet.len = attrs_len; zcl.buf.addBuffer(attrs, attrs_len);
ZigbeeZCLSend_Raw(packet); zigbeeZCLSendCmd(zcl);
ResponseCmndDone(); ResponseCmndDone();
} else { } else {
ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM));
@ -741,44 +713,38 @@ void CmndZbSend(void) {
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params // params
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid ZCLMessage zcl; // prepare the ZCL structure
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
// parse "Device" and "Group" // parse "Device" and "Group"
JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)]; JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)];
if (val_device) { if (val_device) {
device = zigbee_devices.parseDeviceFromName(val_device.getStr()).shortaddr; zcl.shortaddr = zigbee_devices.parseDeviceFromName(val_device.getStr()).shortaddr;
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR(D_ZIGBEE_INVALID_PARAM)); return; } 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)]; JsonParserToken val_group = root[PSTR(D_CMND_ZIGBEE_GROUP)];
if (val_group) { if (val_group) {
groupaddr = val_group.getUInt(); zcl.groupaddr = val_group.getUInt();
} else { // no device nor group } else { // no device nor group
ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return;
return;
} }
} }
// from here, either device has a device shortaddr, or if BAD_SHORTADDR then use group address // from here, either device has a device shortaddr, or if BAD_SHORTADDR then use group address
// Note: groupaddr == 0 is valid // Note: groupaddr == 0 is valid
// read other parameters // read other parameters
cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), cluster); zcl.cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), zcl.cluster);
endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint); zcl.endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), zcl.endpoint);
manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), manuf); zcl.manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), zcl.manuf);
// infer endpoint // infer endpoint
if (BAD_SHORTADDR == device) { if (!zcl.validShortaddr()) {
endpoint = 0xFF; // endpoint not used for group addresses, so use a dummy broadcast endpoint zcl.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 } else if (!zcl.validEndpoint()) { // if it was not already specified, try to guess it
endpoint = zigbee_devices.findFirstEndpoint(device); zcl.endpoint = zigbee_devices.findFirstEndpoint(zcl.shortaddr);
AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZIG: guessing endpoint %d"), endpoint); 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")); ResponseCmndChar_P(PSTR("Missing endpoint"));
return; return;
} }
@ -800,30 +766,19 @@ void CmndZbSend(void) {
} }
// from here we have one and only one command // from here we have one and only one command
// collate information in a ready to send packet zcl.clusterSpecific = false; /* not cluster specific */
ZigbeeZCLSendMessage packet({ zcl.needResponse = false; /* no response */
device, zcl.direct = false; /* discover route */
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
});
if (val_cmd) { if (val_cmd) {
// "Send":{...commands...} // "Send":{...commands...}
// we accept either a string or a JSON object // 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) { } else if (val_read) {
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...] // "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object // we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
packet.cmd = ZCL_READ_ATTRIBUTES; zcl.cmd = ZCL_READ_ATTRIBUTES;
ZbSendRead(val_read, packet); ZbSendRead(val_read, zcl);
} else if (val_write) { } else if (val_write) {
// only KSON object // only KSON object
if (!val_write.isObject()) { if (!val_write.isObject()) {
@ -831,8 +786,8 @@ void CmndZbSend(void) {
return; return;
} }
// "Write":{...attributes...} // "Write":{...attributes...}
packet.cmd = ZCL_WRITE_ATTRIBUTES; zcl.cmd = ZCL_WRITE_ATTRIBUTES;
ZbSendReportWrite(val_write, packet); ZbSendReportWrite(val_write, zcl);
} else if (val_publish) { } else if (val_publish) {
// "Publish":{...attributes...} // "Publish":{...attributes...}
// only KSON object // only KSON object
@ -840,8 +795,8 @@ void CmndZbSend(void) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM));
return; return;
} }
packet.cmd = ZCL_REPORT_ATTRIBUTES; zcl.cmd = ZCL_REPORT_ATTRIBUTES;
ZbSendReportWrite(val_publish, packet); ZbSendReportWrite(val_publish, zcl);
} else if (val_response) { } else if (val_response) {
// "Report":{...attributes...} // "Report":{...attributes...}
// only KSON object // only KSON object
@ -849,13 +804,13 @@ void CmndZbSend(void) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM));
return; return;
} }
packet.cmd = ZCL_READ_ATTRIBUTES_RESPONSE; zcl.cmd = ZCL_READ_ATTRIBUTES_RESPONSE;
ZbSendReportWrite(val_response, packet); ZbSendReportWrite(val_response, zcl);
} else if (val_read_config) { } else if (val_read_config) {
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...] // "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object // we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
packet.cmd = ZCL_READ_REPORTING_CONFIGURATION; zcl.cmd = ZCL_READ_REPORTING_CONFIGURATION;
ZbSendRead(val_read_config, packet); ZbSendRead(val_read_config, zcl);
} else if (val_config) { } else if (val_config) {
// "Config":{...attributes...} // "Config":{...attributes...}
// only JSON object // only JSON object
@ -863,8 +818,8 @@ void CmndZbSend(void) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM)); ResponseCmndChar_P(PSTR(D_ZIGBEE_MISSING_PARAM));
return; return;
} }
packet.cmd = ZCL_CONFIGURE_REPORTING; zcl.cmd = ZCL_CONFIGURE_REPORTING;
ZbSendReportWrite(val_config, packet); ZbSendReportWrite(val_config, zcl);
} else { } else {
Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'")); Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'"));
return; return;