Merge pull request #10804 from s-hadinger/zigbee_refactor_send

Zigbee refactor sending ZCL packets
This commit is contained in:
s-hadinger 2021-02-02 22:02:48 +01:00 committed by GitHub
commit afbf7fa1a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 422 additions and 493 deletions

View File

@ -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++) {

View File

@ -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"

View File

@ -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);

View File

@ -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,
};

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
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);
}
}

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
// - 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

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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(&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)) {
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;