mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-31 22:47:44 +00:00
Merge pull request #6632 from s-hadinger/zigbee_send
Add Zigbee additional commands and sending messages to control devices
This commit is contained in:
commit
1be3b27647
@ -1,6 +1,7 @@
|
|||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* 6.6.0.18 20191010
|
* 6.6.0.18 20191010
|
||||||
* Add command DimmerRange in Light module to support 2 byte dimming ranges from Tuya
|
* Add command DimmerRange in Light module to support 2 byte dimming ranges from Tuya
|
||||||
|
* Add Zigbee additional commands and sending messages to control devices (#6095)
|
||||||
*
|
*
|
||||||
* 6.6.0.17 20191009
|
* 6.6.0.17 20191009
|
||||||
* Add command SetOption34 0..255 to set backlog delay. Default value is 200 (mSeconds) (#6562)
|
* Add command SetOption34 0..255 to set backlog delay. Default value is 200 (mSeconds) (#6562)
|
||||||
|
@ -460,13 +460,17 @@
|
|||||||
// Commands xdrv_23_zigbee.ino
|
// Commands xdrv_23_zigbee.ino
|
||||||
#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin"
|
#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin"
|
||||||
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
|
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
|
||||||
|
#define D_CMND_ZIGBEE_RESET "ZigbeeReset"
|
||||||
|
#define D_JSON_ZIGBEE_CC2530 "CC2530"
|
||||||
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
|
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
|
||||||
#define D_JSON_ZIGBEE_STATUS "ZigbeeStatus"
|
#define D_JSON_ZIGBEE_STATUS "ZigbeeStatus"
|
||||||
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
|
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
|
||||||
#define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent"
|
#define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent"
|
||||||
#define D_JSON_ZIGBEEZCL_RECEIVED "ZigbeeZCLReceived"
|
#define D_JSON_ZIGBEEZCL_RECEIVED "ZigbeeZCLReceived"
|
||||||
#define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived"
|
#define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived"
|
||||||
#define D_JSON_ZIGBEEZCLSENT "ZigbeeZCLSent"
|
#define D_CMND_ZIGBEE_ZCL_SEND "ZigbeeZCLSend"
|
||||||
|
#define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent"
|
||||||
|
#define D_CMND_ZIGBEE_PROBE "ZigbeeProbe"
|
||||||
|
|
||||||
// Commands xdrv_25_A4988_Stepper.ino
|
// Commands xdrv_25_A4988_Stepper.ino
|
||||||
#ifdef USE_A4988_Stepper
|
#ifdef USE_A4988_Stepper
|
||||||
|
@ -98,6 +98,15 @@ public:
|
|||||||
return _buf->len;
|
return _buf->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t addBuffer(const uint8_t *buf2, size_t len2) {
|
||||||
|
if (len() + len2 <= size()) {
|
||||||
|
for (uint32_t i = 0; i < len2; i++) {
|
||||||
|
_buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _buf->len;
|
||||||
|
}
|
||||||
|
|
||||||
size_t addBuffer(const char *buf2, size_t len2) {
|
size_t addBuffer(const char *buf2, size_t len2) {
|
||||||
if (len() + len2 <= size()) {
|
if (len() + len2 <= size()) {
|
||||||
for (uint32_t i = 0; i < len2; i++) {
|
for (uint32_t i = 0; i < len2; i++) {
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#ifdef USE_ZIGBEE
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
|
#define ZIGBEE_VERBOSE // output versbose MQTT Zigbee logs. Will remain active for now
|
||||||
|
|
||||||
typedef uint64_t Z_IEEEAddress;
|
typedef uint64_t Z_IEEEAddress;
|
||||||
typedef uint16_t Z_ShortAddress;
|
typedef uint16_t Z_ShortAddress;
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
typedef struct Z_Device {
|
typedef struct Z_Device {
|
||||||
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
||||||
uint64_t longaddr; // 0x00 means unspecified
|
uint64_t longaddr; // 0x00 means unspecified
|
||||||
|
uint32_t firstSeen; // date when the device was first seen
|
||||||
|
uint32_t lastSeen; // date when the device was last seen
|
||||||
String manufacturerId;
|
String manufacturerId;
|
||||||
String modelId;
|
String modelId;
|
||||||
String friendlyName;
|
String friendlyName;
|
||||||
@ -62,6 +64,9 @@ public:
|
|||||||
void setModelId(uint16_t shortaddr, const char * str);
|
void setModelId(uint16_t shortaddr, const char * str);
|
||||||
void setFriendlyNameId(uint16_t shortaddr, const char * str);
|
void setFriendlyNameId(uint16_t shortaddr, const char * str);
|
||||||
|
|
||||||
|
// device just seen on the network, update the lastSeen field
|
||||||
|
void updateLastSeen(uint16_t shortaddr);
|
||||||
|
|
||||||
// Dump json
|
// Dump json
|
||||||
String dump(uint8_t dump_mode) const;
|
String dump(uint8_t dump_mode) const;
|
||||||
|
|
||||||
@ -83,6 +88,12 @@ private:
|
|||||||
int32_t findShortAddr(uint16_t shortaddr);
|
int32_t findShortAddr(uint16_t shortaddr);
|
||||||
int32_t findLongAddr(uint64_t longaddr);
|
int32_t findLongAddr(uint64_t longaddr);
|
||||||
|
|
||||||
|
void _updateLastSeen(Z_Device &device) {
|
||||||
|
if (&device != nullptr) {
|
||||||
|
device.lastSeen = Rtc.utc_time;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
||||||
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
||||||
};
|
};
|
||||||
@ -140,6 +151,7 @@ int32_t Z_Devices::findClusterEndpoint(const std::vector<uint32_t> & vecOfEleme
|
|||||||
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
||||||
if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create an enrty with both short/long addr null
|
if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create an enrty with both short/long addr null
|
||||||
Z_Device device = { shortaddr, longaddr,
|
Z_Device device = { shortaddr, longaddr,
|
||||||
|
Rtc.utc_time, Rtc.utc_time,
|
||||||
String(), // ManufId
|
String(), // ManufId
|
||||||
String(), // DeviceId
|
String(), // DeviceId
|
||||||
String(), // FriendlyName
|
String(), // FriendlyName
|
||||||
@ -223,17 +235,19 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
|
|||||||
|
|
||||||
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
|
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
|
||||||
if (s_found == l_found) {
|
if (s_found == l_found) {
|
||||||
; // short/long addr match, all good
|
updateLastSeen(shortaddr); // short/long addr match, all good
|
||||||
} else { // they don't match
|
} else { // they don't match
|
||||||
// the device with longaddr got a new shortaddr
|
// the device with longaddr got a new shortaddr
|
||||||
_devices[l_found].shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
_devices[l_found].shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
||||||
// erase the previous shortaddr
|
// erase the previous shortaddr
|
||||||
_devices.erase(_devices.begin() + s_found);
|
_devices.erase(_devices.begin() + s_found);
|
||||||
|
updateLastSeen(shortaddr);
|
||||||
}
|
}
|
||||||
} else if (s_found >= 0) {
|
} else if (s_found >= 0) {
|
||||||
// shortaddr already exists but longaddr not
|
// shortaddr already exists but longaddr not
|
||||||
// add the longaddr to the entry
|
// add the longaddr to the entry
|
||||||
_devices[s_found].longaddr = longaddr;
|
_devices[s_found].longaddr = longaddr;
|
||||||
|
updateLastSeen(shortaddr);
|
||||||
} else if (l_found >= 0) {
|
} else if (l_found >= 0) {
|
||||||
// longaddr entry exists, update shortaddr
|
// longaddr entry exists, update shortaddr
|
||||||
_devices[l_found].shortaddr = shortaddr;
|
_devices[l_found].shortaddr = shortaddr;
|
||||||
@ -253,7 +267,8 @@ void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) {
|
|||||||
uint32_t ep_profile = (endpoint << 16);
|
uint32_t ep_profile = (endpoint << 16);
|
||||||
Z_Device &device = getShortAddr(shortaddr);
|
Z_Device &device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
if (findEndpointInVector(device.endpoints, ep_profile) < 0) { // TODO search only on enpoint
|
_updateLastSeen(device);
|
||||||
|
if (findEndpointInVector(device.endpoints, ep_profile) < 0) {
|
||||||
device.endpoints.push_back(ep_profile);
|
device.endpoints.push_back(ep_profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,8 +278,9 @@ void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t
|
|||||||
uint32_t ep_profile = (endpoint << 16) | profileId;
|
uint32_t ep_profile = (endpoint << 16) | profileId;
|
||||||
Z_Device &device = getShortAddr(shortaddr);
|
Z_Device &device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
int32_t found = findEndpointInVector(device.endpoints, ep_profile);
|
int32_t found = findEndpointInVector(device.endpoints, ep_profile);
|
||||||
if (found < 0) { // TODO search only on enpoint
|
if (found < 0) {
|
||||||
device.endpoints.push_back(ep_profile);
|
device.endpoints.push_back(ep_profile);
|
||||||
} else {
|
} else {
|
||||||
device.endpoints[found] = ep_profile;
|
device.endpoints[found] = ep_profile;
|
||||||
@ -275,6 +291,7 @@ void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluste
|
|||||||
if (!shortaddr) { return; }
|
if (!shortaddr) { return; }
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
uint32_t ep_cluster = (endpoint << 16) | cluster;
|
uint32_t ep_cluster = (endpoint << 16) | cluster;
|
||||||
if (!out) {
|
if (!out) {
|
||||||
if (!findInVector(device.clusters_in, ep_cluster)) {
|
if (!findInVector(device.clusters_in, ep_cluster)) {
|
||||||
@ -290,6 +307,8 @@ void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluste
|
|||||||
// Look for the best endpoint match to send a command for a specific Cluster ID
|
// Look for the best endpoint match to send a command for a specific Cluster ID
|
||||||
// return 0x00 if none found
|
// return 0x00 if none found
|
||||||
uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){
|
uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){
|
||||||
|
int32_t short_found = findShortAddr(shortaddr);
|
||||||
|
if (short_found < 0) return 0; // avoid creating an entry if the device was never seen
|
||||||
Z_Device &device = getShortAddr(shortaddr);
|
Z_Device &device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return 0; } // don't crash if not found
|
if (&device == nullptr) { return 0; } // don't crash if not found
|
||||||
int32_t found = findClusterEndpoint(device.clusters_in, cluster);
|
int32_t found = findClusterEndpoint(device.clusters_in, cluster);
|
||||||
@ -304,19 +323,29 @@ uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){
|
|||||||
void Z_Devices::setManufId(uint16_t shortaddr, const char * str) {
|
void Z_Devices::setManufId(uint16_t shortaddr, const char * str) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
device.manufacturerId = str;
|
device.manufacturerId = str;
|
||||||
}
|
}
|
||||||
void Z_Devices::setModelId(uint16_t shortaddr, const char * str) {
|
void Z_Devices::setModelId(uint16_t shortaddr, const char * str) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
device.modelId = str;
|
device.modelId = str;
|
||||||
}
|
}
|
||||||
void Z_Devices::setFriendlyNameId(uint16_t shortaddr, const char * str) {
|
void Z_Devices::setFriendlyNameId(uint16_t shortaddr, const char * str) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&device == nullptr) { return; } // don't crash if not found
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
device.friendlyName = str;
|
device.friendlyName = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// device just seen on the network, update the lastSeen field
|
||||||
|
void Z_Devices::updateLastSeen(uint16_t shortaddr) {
|
||||||
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
|
if (&device == nullptr) { return; } // don't crash if not found
|
||||||
|
_updateLastSeen(device);
|
||||||
|
}
|
||||||
|
|
||||||
// Dump the internal memory of Zigbee devices
|
// Dump the internal memory of Zigbee devices
|
||||||
// Mode = 1: simple dump of devices addresses and names
|
// Mode = 1: simple dump of devices addresses and names
|
||||||
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters
|
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters
|
||||||
|
@ -53,6 +53,7 @@ public:
|
|||||||
uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
|
uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
|
||||||
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
|
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
|
||||||
uint32_t timestamp) {
|
uint32_t timestamp) {
|
||||||
|
#ifdef ZIGBEE_VERBOSE
|
||||||
char hex_char[_payload.len()*2+2];
|
char hex_char[_payload.len()*2+2];
|
||||||
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
|
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
|
||||||
@ -73,6 +74,7 @@ public:
|
|||||||
ResponseJsonEnd(); // append '}'
|
ResponseJsonEnd(); // append '}'
|
||||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
|
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
|
||||||
@ -134,17 +136,6 @@ private:
|
|||||||
SBuffer _payload;
|
SBuffer _payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
char Hex36Char(uint8_t value) {
|
|
||||||
// convert an integer from 0 to 46, to a single digit 0-9A-Z
|
|
||||||
if (value < 10) {
|
|
||||||
return '0' + value;
|
|
||||||
} else if (value < 46) {
|
|
||||||
return 'A' + value - 10;
|
|
||||||
} else {
|
|
||||||
return '?'; // out of range
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zigbee ZCL converters
|
// Zigbee ZCL converters
|
||||||
|
|
||||||
// from https://github.com/Koenkk/zigbee-shepherd-converters/blob/638d29f0cace6343052b9a4e7fd60980fa785479/converters/fromZigbee.js#L55
|
// from https://github.com/Koenkk/zigbee-shepherd-converters/blob/638d29f0cace6343052b9a4e7fd60980fa785479/converters/fromZigbee.js#L55
|
||||||
@ -299,19 +290,29 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
i += buf.get8(i) + 1;
|
i += buf.get8(i) + 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
case 0x08: // data8
|
case 0x08: // data8
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 0x18: // map8
|
case 0x18: // map8
|
||||||
i++;
|
{
|
||||||
|
uint8_t uint8_val = buf.get8(i);
|
||||||
|
i += 1;
|
||||||
|
json[attrid_str] = uint8_val;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x09: // data16
|
||||||
case 0x19: // map16
|
case 0x19: // map16
|
||||||
i += 2;
|
{
|
||||||
|
uint16_t uint16_val = buf.get16(i);
|
||||||
|
i += 2;
|
||||||
|
json[attrid_str] = uint16_val;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x0B: // data32
|
||||||
case 0x1B: // map32
|
case 0x1B: // map32
|
||||||
i += 4;
|
{
|
||||||
|
uint32_t uint32_val = buf.get32(i);
|
||||||
|
i += 4;
|
||||||
|
json[attrid_str] = uint32_val;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
// enum
|
// enum
|
||||||
case 0x30: // enum8
|
case 0x30: // enum8
|
||||||
@ -319,6 +320,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
i += attrtype - 0x2F;
|
i += attrtype - 0x2F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// TODO
|
||||||
case 0x39: // float
|
case 0x39: // float
|
||||||
i += 4;
|
i += 4;
|
||||||
break;
|
break;
|
||||||
@ -345,9 +347,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// Other un-implemented data types
|
// Other un-implemented data types
|
||||||
case 0x09: // data16
|
|
||||||
case 0x0A: // data24
|
case 0x0A: // data24
|
||||||
case 0x0B: // data32
|
|
||||||
case 0x0C: // data40
|
case 0x0C: // data40
|
||||||
case 0x0D: // data48
|
case 0x0D: // data48
|
||||||
case 0x0E: // data56
|
case 0x0E: // data56
|
||||||
@ -381,7 +381,6 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
|
|||||||
|
|
||||||
|
|
||||||
// First pass, parse all attributes in their native format
|
// First pass, parse all attributes in their native format
|
||||||
// The key is 32 bits, high 16 bits is cluserid, low 16 bits is attribute id
|
|
||||||
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
||||||
uint32_t i = offset;
|
uint32_t i = offset;
|
||||||
uint32_t len = _payload.len();
|
uint32_t len = _payload.len();
|
||||||
@ -390,9 +389,9 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
|||||||
uint16_t attrid = _payload.get16(i);
|
uint16_t attrid = _payload.get16(i);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
char shortaddr[16];
|
char key[16];
|
||||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("%c_%04X_%04X"),
|
snprintf_P(key, sizeof(key), PSTR("%04X_%02X_%04X"),
|
||||||
Hex36Char(_cmd_id), _cluster_id, attrid);
|
_cluster_id, _cmd_id, attrid);
|
||||||
|
|
||||||
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
|
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
|
||||||
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
||||||
@ -400,7 +399,7 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
|||||||
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
|
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += parseSingleAttribute(json, shortaddr, _payload, i, len);
|
i += parseSingleAttribute(json, key, _payload, i, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,11 +414,11 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||||||
uint8_t status = _payload.get8(i++);
|
uint8_t status = _payload.get8(i++);
|
||||||
|
|
||||||
if (0 == status) {
|
if (0 == status) {
|
||||||
char shortaddr[16];
|
char key[16];
|
||||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("%c_%04X_%04X"),
|
snprintf_P(key, sizeof(key), PSTR("%04X_%02X_%04X"),
|
||||||
Hex36Char(_cmd_id), _cluster_id, attrid);
|
_cluster_id, _cmd_id, attrid);
|
||||||
|
|
||||||
i += parseSingleAttribute(json, shortaddr, _payload, i, len);
|
i += parseSingleAttribute(json, key, _payload, i, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +431,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
|||||||
uint32_t len = _payload.len();
|
uint32_t len = _payload.len();
|
||||||
|
|
||||||
char attrid_str[12];
|
char attrid_str[12];
|
||||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("s_%04X_%02X"), _cluster_id, _cmd_id);
|
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cmd_id, _cluster_id);
|
||||||
|
|
||||||
char hex_char[_payload.len()*2+2];
|
char hex_char[_payload.len()*2+2];
|
||||||
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
||||||
@ -456,51 +455,51 @@ const float Z_10 PROGMEM = 10.0f;
|
|||||||
|
|
||||||
// list of post-processing directives
|
// list of post-processing directives
|
||||||
const Z_AttributeConverter Z_PostProcess[] = {
|
const Z_AttributeConverter Z_PostProcess[] = {
|
||||||
{ "?_0000_0004", nullptr, &Z_ManufKeep, nullptr }, // record Manufacturer
|
{ "0000_0?_0004", nullptr, &Z_ManufKeep, nullptr }, // record Manufacturer
|
||||||
{ "?_0000_0005", nullptr, &Z_ModelKeep, nullptr }, // record Model
|
{ "0000_0?_0005", nullptr, &Z_ModelKeep, nullptr }, // record Model
|
||||||
|
|
||||||
{ "?_0000_0000", "ZCLVersion", &Z_Copy, nullptr },
|
{ "0000_0?_0000", "ZCLVersion", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0001", "AppVersion", &Z_Copy, nullptr },
|
{ "0000_0?_0001", "AppVersion", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0002", "StackVersion", &Z_Copy, nullptr },
|
{ "0000_0?_0002", "StackVersion", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0003", "HWVersion", &Z_Copy, nullptr },
|
{ "0000_0?_0003", "HWVersion", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0004", "Manufacturer", &Z_Copy, nullptr },
|
{ "0000_0?_0004", "Manufacturer", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr },
|
{ "0000_0?_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr },
|
||||||
{ "?_0000_0006", "DateCode", &Z_Copy, nullptr },
|
{ "0000_0?_0006", "DateCode", &Z_Copy, nullptr },
|
||||||
{ "?_0000_0007", "PowerSource", &Z_Copy, nullptr },
|
{ "0000_0?_0007", "PowerSource", &Z_Copy, nullptr },
|
||||||
{ "?_0000_4000", "SWBuildID", &Z_Copy, nullptr },
|
{ "0000_0?_4000", "SWBuildID", &Z_Copy, nullptr },
|
||||||
{ "A_0000_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0000_0?_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0400_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux)
|
{ "0400_0A_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux)
|
||||||
{ "A_0400_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
{ "0400_0A_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
||||||
{ "A_0400_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0400_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0401_0000", "LevelStatus", &Z_Copy, nullptr }, // Illuminance (in Lux)
|
{ "0401_0A_0000", "LevelStatus", &Z_Copy, nullptr }, // Illuminance (in Lux)
|
||||||
{ "A_0401_0001", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
{ "0401_0A_0001", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
|
||||||
{ "A_0401_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0401_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0402_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature
|
{ "0402_0A_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature
|
||||||
{ "A_0402_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0402_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0403_0000", D_JSON_PRESSURE_UNIT, &Z_Const_Keep, (void*) D_UNIT_PRESSURE}, // Pressure Unit
|
{ "0403_0A_0000", D_JSON_PRESSURE_UNIT, &Z_Const_Keep, (void*) D_UNIT_PRESSURE}, // Pressure Unit
|
||||||
{ "A_0403_0000", D_JSON_PRESSURE, &Z_Copy, nullptr }, // Pressure
|
{ "0403_0A_0000", D_JSON_PRESSURE, &Z_Copy, nullptr }, // Pressure
|
||||||
{ "A_0403_????", nullptr, &Z_Remove, nullptr }, // Remove all other Pressure values
|
{ "0403_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other Pressure values
|
||||||
|
|
||||||
{ "A_0404_0000", D_JSON_FLOWRATE, &Z_ConvFloatDivider, (void*) &Z_10 }, // Flow (in m3/h)
|
{ "0404_0A_0000", D_JSON_FLOWRATE, &Z_ConvFloatDivider, (void*) &Z_10 }, // Flow (in m3/h)
|
||||||
{ "A_0404_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0404_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0405_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity
|
{ "0405_0A_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity
|
||||||
{ "A_0405_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0405_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
{ "A_0406_0000", "Occupancy", &Z_Copy, nullptr }, // Occupancy (map8)
|
{ "0406_0A_0000", "Occupancy", &Z_Copy, nullptr }, // Occupancy (map8)
|
||||||
{ "A_0406_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
|
{ "0406_0A_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
|
||||||
{ "A_0406_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
{ "0406_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
|
||||||
|
|
||||||
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
||||||
{ "A_0000_FF01", nullptr, &Z_AqaraSensor, nullptr }, // Occupancy (map8)
|
{ "0000_0A_FF01", nullptr, &Z_AqaraSensor, nullptr }, // Occupancy (map8)
|
||||||
// // 0x0b04 Electrical Measurement
|
// // 0x0b04 Electrical Measurement
|
||||||
// { "A_0B04_0100", "DCVoltage", &Z_Copy, nullptr }, // Occupancy (map8)
|
// { "0B04_0100", "DCVoltage", &Z_Copy, nullptr }, //
|
||||||
// { "A_0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
|
// { "0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, //
|
||||||
// { "A_0B04_????", "", &Z_Remove, nullptr }, // Remove all other values
|
// { "0B04_????", "", &Z_Remove, nullptr }, //
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -573,13 +572,13 @@ int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, Js
|
|||||||
}
|
}
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
#define ZCL_MODELID "A_0000_0005" // Cmd 0x0A - Cluster 0x0000, attribute 0x05
|
// #define ZCL_MODELID "A_0000_0005" // Cmd 0x0A - Cluster 0x0000, attribute 0x05
|
||||||
#define ZCL_TEMPERATURE "A_0402_0000" // Cmd 0x0A - Cluster 0x0402, attribute 0x00
|
// #define ZCL_TEMPERATURE "A_0402_0000" // Cmd 0x0A - Cluster 0x0402, attribute 0x00
|
||||||
#define ZCL_PRESSURE "A_0403_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
// #define ZCL_PRESSURE "A_0403_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
||||||
#define ZCL_PRESSURE_SCALED "A_0403_0010" // Cmd 0x0A - Cluster 0x0403, attribute 0x10
|
// #define ZCL_PRESSURE_SCALED "A_0403_0010" // Cmd 0x0A - Cluster 0x0403, attribute 0x10
|
||||||
#define ZCL_PRESSURE_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
|
// #define ZCL_PRESSURE_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
|
||||||
#define ZCL_HUMIDITY "A_0405_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
// #define ZCL_HUMIDITY "A_0405_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
|
||||||
#define ZCL_LUMI_WEATHER "A_0000_FF01" // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
// #define ZCL_LUMI_WEATHER "A_0000_FF01" // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
|
||||||
|
|
||||||
// Cluster Specific commands
|
// Cluster Specific commands
|
||||||
#define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
|
#define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
|
||||||
|
@ -322,10 +322,10 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
|||||||
ZI_WAIT(10000) // wait for 10 seconds for Tasmota to stabilize
|
ZI_WAIT(10000) // wait for 10 seconds for Tasmota to stabilize
|
||||||
ZI_ON_ERROR_GOTO(50)
|
ZI_ON_ERROR_GOTO(50)
|
||||||
|
|
||||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
|
//ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
|
||||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
|
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
|
||||||
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
|
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
|
||||||
ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s
|
ZI_WAIT_RECV_FUNC(5000, ZBR_RESET, &Z_Reboot) // timeout 5s
|
||||||
ZI_WAIT(100)
|
ZI_WAIT(100)
|
||||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
|
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
|
||||||
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
|
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
|
||||||
@ -356,7 +356,7 @@ ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
|
|||||||
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
|
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
|
||||||
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
|
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
|
||||||
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
||||||
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
|
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // memorize info
|
||||||
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
|
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
|
||||||
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
||||||
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
|
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
|
||||||
|
@ -73,6 +73,39 @@ int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
|
||||||
|
|
||||||
|
int32_t Z_Reboot(int32_t res, class SBuffer &buf) {
|
||||||
|
// print information about the reboot of device
|
||||||
|
// 4180.02.02.00.02.06.03
|
||||||
|
//
|
||||||
|
uint8_t reason = buf.get8(2);
|
||||||
|
uint8_t transport_rev = buf.get8(3);
|
||||||
|
uint8_t product_id = buf.get8(4);
|
||||||
|
uint8_t major_rel = buf.get8(5);
|
||||||
|
uint8_t minor_rel = buf.get8(6);
|
||||||
|
uint8_t hw_rev = buf.get8(7);
|
||||||
|
char reason_str[12];
|
||||||
|
|
||||||
|
if (reason > 3) { reason = 3; }
|
||||||
|
GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason);
|
||||||
|
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||||
|
"\"Status\":%d,\"Message\":\"%s\",\"RestartReason\":\"%s\""
|
||||||
|
",\"MajorRel\":%d,\"MinorRel\":%d}}"),
|
||||||
|
ZIGBEE_STATUS_BOOT, "CC2530 booted", reason_str,
|
||||||
|
major_rel, minor_rel);
|
||||||
|
|
||||||
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||||
|
XdrvRulesProcess();
|
||||||
|
|
||||||
|
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
||||||
|
return 0; // version 2.6.x is ok
|
||||||
|
} else {
|
||||||
|
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
||||||
// check that the version is supported
|
// check that the version is supported
|
||||||
// typical version for ZNP 1.2
|
// typical version for ZNP 1.2
|
||||||
@ -178,6 +211,8 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
|
|||||||
uint8_t descriptorCapabilities = buf.get8(19);
|
uint8_t descriptorCapabilities = buf.get8(19);
|
||||||
|
|
||||||
if (0 == status) {
|
if (0 == status) {
|
||||||
|
zigbee_devices.updateLastSeen(nwkAddr);
|
||||||
|
|
||||||
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
|
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
|
||||||
if (deviceType > 3) { deviceType = 3; }
|
if (deviceType > 3) { deviceType = 3; }
|
||||||
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
||||||
@ -290,9 +325,9 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
|
||||||
uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000);
|
uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000);
|
||||||
Serial.printf(">>> Endpoint is 0x%02X for cluster 0x%04X\n", cluster, 0x0000);
|
//Serial.printf(">>> Endpoint is 0x%02X for cluster 0x%04X\n", cluster, 0x0000);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01); // TODO
|
Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01); // TODO, do we need tarnsacId counter?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@ -335,12 +370,15 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
|||||||
uint32_t timestamp = buf.get32(13);
|
uint32_t timestamp = buf.get32(13);
|
||||||
uint8_t seqnumber = buf.get8(17);
|
uint8_t seqnumber = buf.get8(17);
|
||||||
|
|
||||||
|
zigbee_devices.updateLastSeen(srcaddr);
|
||||||
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
|
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
|
||||||
|
|
||||||
|
#ifdef ZIGBEE_VERBOSE
|
||||||
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
|
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
|
||||||
srcendpoint, dstendpoint, wasbroadcast,
|
srcendpoint, dstendpoint, wasbroadcast,
|
||||||
linkquality, securityuse, seqnumber,
|
linkquality, securityuse, seqnumber,
|
||||||
timestamp);
|
timestamp);
|
||||||
|
#endif
|
||||||
|
|
||||||
char shortaddr[8];
|
char shortaddr[8];
|
||||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
|
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
|
||||||
const uint8_t ZIGBEE_SOF = 0xFE;
|
const uint8_t ZIGBEE_SOF = 0xFE;
|
||||||
|
const uint8_t ZIGBEE_SOF_ALT = 0xFF;
|
||||||
|
|
||||||
//#define Z_USE_SOFTWARE_SERIAL
|
//#define Z_USE_SOFTWARE_SERIAL
|
||||||
|
|
||||||
@ -36,10 +37,12 @@ TasmotaSerial *ZigbeeSerial = nullptr;
|
|||||||
|
|
||||||
|
|
||||||
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN
|
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN
|
||||||
"|" D_CMND_ZIGBEE_STATUS;
|
"|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_ZCL_SEND
|
||||||
|
"|" D_CMND_ZIGBEE_PROBE;
|
||||||
|
|
||||||
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
||||||
&CmndZigbeeStatus };
|
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeZCLSend,
|
||||||
|
&CmndZigbeeProbe };
|
||||||
|
|
||||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||||
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
|
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
|
||||||
@ -140,6 +143,13 @@ void ZigbeeInput(void)
|
|||||||
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
|
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
|
||||||
zigbee_frame_len = 5;
|
zigbee_frame_len = 5;
|
||||||
fcs = ZIGBEE_SOF;
|
fcs = ZIGBEE_SOF;
|
||||||
|
// there is a rare race condition when an interrupt occurs when receiving the first byte
|
||||||
|
// in this case the first bit (lsb) is missed and Tasmota receives 0xFF instead of 0xFE
|
||||||
|
// We forgive this mistake, and next bytes are automatically resynchronized
|
||||||
|
if (ZIGBEE_SOF_ALT == zigbee_in_byte) {
|
||||||
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte);
|
||||||
|
zigbee_in_byte = ZIGBEE_SOF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
|
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
|
||||||
@ -189,10 +199,12 @@ void ZigbeeInput(void)
|
|||||||
|
|
||||||
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
|
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
|
||||||
|
|
||||||
|
#ifdef ZIGBEE_VERBOSE
|
||||||
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
|
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
|
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
|
||||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#endif
|
||||||
|
|
||||||
// now process the message
|
// now process the message
|
||||||
ZigbeeProcessInput(znp_buffer);
|
ZigbeeProcessInput(znp_buffer);
|
||||||
@ -234,6 +246,34 @@ void ZigbeeInit(void)
|
|||||||
* Commands
|
* Commands
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
uint32_t strToUInt(const JsonVariant &val) {
|
||||||
|
// if the string starts with 0x, it is considered Hex, otherwise it is an int
|
||||||
|
if (val.is<unsigned int>()) {
|
||||||
|
return val.as<unsigned int>();
|
||||||
|
} else {
|
||||||
|
if (val.is<char*>()) {
|
||||||
|
return strtoull(val.as<char*>(), nullptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // couldn't parse anything
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = "2112000F0100"; // Z_SREQ | Z_SYS, SYS_OSAL_NV_DELETE, 0x0F00 id, 0x0001 len
|
||||||
|
// Do a factory reset of the CC2530
|
||||||
|
void CmndZigbeeReset(void) {
|
||||||
|
if (ZigbeeSerial) {
|
||||||
|
switch (XdrvMailbox.payload) {
|
||||||
|
case 1:
|
||||||
|
ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET));
|
||||||
|
restart_flag = 2;
|
||||||
|
ResponseCmndChar(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ResponseCmndChar(D_JSON_ONE_TO_RESET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CmndZigbeeStatus(void) {
|
void CmndZigbeeStatus(void) {
|
||||||
if (ZigbeeSerial) {
|
if (ZigbeeSerial) {
|
||||||
String dump = zigbee_devices.dump(XdrvMailbox.payload);
|
String dump = zigbee_devices.dump(XdrvMailbox.payload);
|
||||||
@ -288,12 +328,148 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
|||||||
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
|
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
|
||||||
}
|
}
|
||||||
|
#ifdef ZIGBEE_VERBOSE
|
||||||
// Now send a MQTT message to report the sent message
|
// Now send a MQTT message to report the sent message
|
||||||
char hex_char[(len * 2) + 2];
|
char hex_char[(len * 2) + 2];
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPSENT "\":\"%s\"}"),
|
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPSENT "\":\"%s\"}"),
|
||||||
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
|
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
|
||||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPSENT));
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPSENT));
|
||||||
XdrvRulesProcess();
|
XdrvRulesProcess();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, uint8_t transacId = 1) {
|
||||||
|
SBuffer buf(25+len);
|
||||||
|
buf.add8(Z_SREQ | Z_AF); // 24
|
||||||
|
buf.add8(AF_DATA_REQUEST); // 01
|
||||||
|
buf.add16(dtsAddr);
|
||||||
|
buf.add8(endpoint); // dest endpoint
|
||||||
|
buf.add8(0x01); // source endpoint
|
||||||
|
buf.add16(clusterId);
|
||||||
|
buf.add8(transacId); // transacId
|
||||||
|
buf.add8(0x30); // 30 options
|
||||||
|
buf.add8(0x1E); // 1E radius
|
||||||
|
|
||||||
|
buf.add8(3 + len);
|
||||||
|
buf.add8(0x10 | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
|
||||||
|
buf.add8(transacId); // Transaction Sequance Number
|
||||||
|
buf.add8(cmdId);
|
||||||
|
buf.addBuffer(msg, len); // add the payload
|
||||||
|
|
||||||
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int8_t hexValue(char c) {
|
||||||
|
if ((c >= '0') && (c <= '9')) {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if ((c >= 'A') && (c <= 'F')) {
|
||||||
|
return 10 + c - 'A';
|
||||||
|
}
|
||||||
|
if ((c >= 'a') && (c <= 'f')) {
|
||||||
|
return 10 + c - 'a';
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t parseHex(const char **data, size_t max_len = 8) {
|
||||||
|
uint32_t ret = 0;
|
||||||
|
for (uint32_t i = 0; i < max_len; i++) {
|
||||||
|
int8_t v = hexValue(**data);
|
||||||
|
if (v < 0) { break; } // non hex digit, we stop parsing
|
||||||
|
ret = (ret << 4) | v;
|
||||||
|
*data += 1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndZigbeeZCLSend(void) {
|
||||||
|
char parm_uc[12]; // used to convert JSON keys to uppercase
|
||||||
|
// ZigbeeZCLSend { "dst":"0x1234", "cluster":"0x0300", "endpoint":"0x01", "cmd":10, "data":"AABBCC" }
|
||||||
|
char dataBufUc[XdrvMailbox.data_len];
|
||||||
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
||||||
|
RemoveSpace(dataBufUc);
|
||||||
|
if (strlen(dataBufUc) < 8) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
|
||||||
|
|
||||||
|
DynamicJsonBuffer jsonBuf;
|
||||||
|
JsonObject &json = jsonBuf.parseObject(dataBufUc);
|
||||||
|
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
|
||||||
|
|
||||||
|
// params
|
||||||
|
uint16_t dstAddr = 0xFFFF; // 0xFFFF is braodcast, so considered invalid
|
||||||
|
uint16_t clusterId = 0x0000; // 0x0000 is a valid default value
|
||||||
|
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
|
||||||
|
uint8_t cmd = ZCL_READ_ATTRIBUTES; // default command is READ_ATTRIBUTES
|
||||||
|
bool clusterSpecific = false;
|
||||||
|
const char* data = ""; // empty string is valid
|
||||||
|
|
||||||
|
UpperCase_P(parm_uc, PSTR("device"));
|
||||||
|
if (json.containsKey(parm_uc)) { dstAddr = strToUInt(json[parm_uc]); }
|
||||||
|
UpperCase_P(parm_uc, PSTR("endpoint"));
|
||||||
|
if (json.containsKey(parm_uc)) { endpoint = strToUInt(json[parm_uc]); }
|
||||||
|
UpperCase_P(parm_uc, PSTR("cmd"));
|
||||||
|
if (json.containsKey(parm_uc)) { data = json[parm_uc].as<const char*>(); }
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// First delimiter is '_' for a global command, or '!' for a cluster specific commanc
|
||||||
|
clusterId = parseHex(&data, 4);
|
||||||
|
|
||||||
|
// delimiter
|
||||||
|
if (('_' == *data) || ('!' == *data)) {
|
||||||
|
if ('!' == *data) { clusterSpecific = true; }
|
||||||
|
data++;
|
||||||
|
} else {
|
||||||
|
ResponseCmndChar("Wrong delimiter for payload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// parse cmd number
|
||||||
|
cmd = parseHex(&data, 2);
|
||||||
|
|
||||||
|
// move to end of payload
|
||||||
|
// delimiter is optional
|
||||||
|
if ('/' == *data) { data++; } // skip delimiter
|
||||||
|
|
||||||
|
size_t size = strlen(data);
|
||||||
|
SBuffer buf((size+2)/2); // actual bytes buffer for data
|
||||||
|
|
||||||
|
while (*data) {
|
||||||
|
uint8_t code = parseHex(&data, 2);
|
||||||
|
buf.add8(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == endpoint) {
|
||||||
|
// endpoint is not specified, let's try to find it from shortAddr
|
||||||
|
endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, clusterId);
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeZCLSend: guessing endpoint 0x%02X"), endpoint);
|
||||||
|
}
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeZCLSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
|
||||||
|
dstAddr, clusterId, endpoint, cmd, data);
|
||||||
|
|
||||||
|
if (0 == endpoint) {
|
||||||
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("CmndZigbeeZCLSend: unspecified endpoint"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything is good, we can send the command
|
||||||
|
ZigbeeZCLSend(dstAddr, clusterId, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
|
||||||
|
ResponseCmndDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe a specific device to get its endpoints and supported clusters
|
||||||
|
void CmndZigbeeProbe(void) {
|
||||||
|
char dataBufUc[XdrvMailbox.data_len];
|
||||||
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
||||||
|
RemoveSpace(dataBufUc);
|
||||||
|
if (strlen(dataBufUc) < 3) { ResponseCmndChar("Invalid destination"); return; }
|
||||||
|
|
||||||
|
// TODO, for now ignore friendly names
|
||||||
|
uint16_t shortaddr = strtoull(dataBufUc, nullptr, 0);
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeScan: short addr 0x%04X"), shortaddr);
|
||||||
|
|
||||||
|
// everything is good, we can send the command
|
||||||
|
Z_SendActiveEpReq(shortaddr);
|
||||||
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow or Deny pairing of new Zigbee devices
|
// Allow or Deny pairing of new Zigbee devices
|
||||||
|
Loading…
x
Reference in New Issue
Block a user