Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command

This commit is contained in:
Stephan Hadinger 2019-09-29 15:38:26 +02:00
parent 4c21e46512
commit d86cd34905
7 changed files with 1539 additions and 1005 deletions

View File

@ -3,6 +3,7 @@
* Change command Tariffx to allow time entries like 23 (hours), 1320 (minutes) or 23:00. NOTE: As this is development branch previous tariffs are lost! (#6488)
* Remove support for define USE_DS18x20_LEGACY and legacy DS18x20 driver (#6486)
* Add initial support for MQTT logging using command MqttLog <loglevel> (#6498)
* Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command
*
* 6.6.0.13 20190922
* Add command EnergyReset4 x,x to initialize total usage for two tarrifs

View File

@ -456,6 +456,7 @@
// Commands xdrv_23_zigbee.ino
#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin"
#define D_CMND_ZIGBEE_DUMP "ZigbeeDump"
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
#define D_JSON_ZIGBEE_STATUS "ZigbeeStatus"
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"

View File

@ -133,6 +133,16 @@ private:
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
@ -374,17 +384,17 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
uint32_t i = offset;
uint32_t len = _payload.len();
uint32_t attrid = _cluster_id << 16; // set high 16 bits with cluster id
while (len + offset - i >= 3) {
attrid = (attrid & 0xFFFF0000) | _payload.get16(i); // get lower 16 bits
while (len - i >= 3) {
uint16_t attrid = _payload.get16(i);
i += 2;
char shortaddr[12];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%08X"), attrid);
char shortaddr[16];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("%c_%04X_%04X"),
Hex36Char(_cmd_id), _cluster_id, attrid);
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
if (0x0000FF01 == attrid) {
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
if (0x42 == _payload.get8(i)) {
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
}
@ -394,14 +404,13 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
}
// Parse non-normalized attributes
// The key is 24 bits, high 16 bits is cluserid, low 8 bits is command id
// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
uint32_t i = offset;
uint32_t len = _payload.len();
uint32_t attrid = _cluster_id << 8 | _cmd_id;
char attrid_str[12];
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("0x%06X"), attrid); // 24 bits
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("s_%04X_%02X"), _cluster_id, _cmd_id);
char hex_char[_payload.len()*2+2];
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
@ -409,211 +418,333 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
json[attrid_str] = hex_char;
}
#define ZCL_MODELID "0x00000005" // Cluster 0x0000, attribute 0x05
#define ZCL_TEMPERATURE "0x04020000" // Cluster 0x0402, attribute 0x00
#define ZCL_PRESSURE "0x04030000" // Cluster 0x0403, attribute 0x00
#define ZCL_PRESSURE_SCALED "0x04030010" // Cluster 0x0403, attribute 0x10
#define ZCL_PRESSURE_SCALE "0x04030014" // Cluster 0x0403, attribute 0x14
#define ZCL_HUMIDITY "0x04050000" // Cluster 0x0403, attribute 0x00
#define ZCL_LUMI_WEATHER "0x0000FF01" // Cluster 0x0000, attribute 0xFF01 - proprietary
// return value:
// 0 = keep initial value
// 1 = remove initial value
typedef int32_t (*Z_AttrConverter)(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
typedef struct Z_AttributeConverter {
const char * filter;
const char * name;
Z_AttrConverter func;
void * param;
} Z_AttributeConverter;
#define ZCL_OO_OFF "0x000600" // Cluster 0x0006, cmd 0x00 - On/Off - Off
#define ZCL_OO_ON "0x000601" // Cluster 0x0006, cmd 0x01 - On/Off - On
#define ZCL_COLORTEMP_MOVE "0x03000A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
#define ZCL_LC_MOVE "0x000800" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
#define ZCL_LC_MOVE_1 "0x000801" // Cluster 0x0008, cmd 0x01, Level Control Move
#define ZCL_LC_STEP "0x000802" // Cluster 0x0008, cmd 0x02, Level Control Step
#define ZCL_LC_STOP "0x000803" // Cluster 0x0008, cmd 0x03, Level Control Stop
#define ZCL_LC_MOVE_WOO "0x000804" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
#define ZCL_LC_MOVE_1_WOO "0x000805" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
#define ZCL_LC_STEP_WOO "0x000806" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
#define ZCL_LC_STOP_WOO "0x000807" // Cluster 0x0008, cmd 0x07, Level Control Stop
const float Z_100 PROGMEM = 100.0f;
const float Z_10 PROGMEM = 10.0f;
void ZCLFrame::postProcessAttributes(JsonObject& json) {
const __FlashStringHelper *key;
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] = {
{ "A_0000_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr }, // ModelID
// ModelID ZCL 3.2
key = F(ZCL_MODELID);
if (json.containsKey(key)) {
json[F(D_JSON_MODEL D_JSON_ID)] = json[key];
json.remove(key);
{ "A_0400_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux)
{ "A_0400_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
{ "A_0400_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ "A_0401_0000", "LevelStatus", &Z_Copy, nullptr }, // Illuminance (in Lux)
{ "A_0401_0001", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
{ "A_0401_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ "A_0402_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature
{ "A_0402_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ "A_0403_0000", D_JSON_PRESSURE_UNIT, &Z_Const_Keep, (void*) D_UNIT_PRESSURE}, // Pressure Unit
{ "A_0403_0000", D_JSON_PRESSURE, &Z_Copy, nullptr }, // Pressure
{ "A_0403_????", nullptr, &Z_Remove, nullptr }, // Remove all other Pressure values
{ "A_0404_0000", D_JSON_FLOWRATE, &Z_ConvFloatDivider, (void*) &Z_10 }, // Flow (in m3/h)
{ "A_0404_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ "A_0405_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity
{ "A_0405_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ "A_0406_0000", "Occupancy", &Z_Copy, nullptr }, // Occupancy (map8)
{ "A_0406_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
{ "A_0406_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
{ "A_0000_FF01", nullptr, &Z_AqaraSensor, nullptr }, // Occupancy (map8)
// // 0x0b04 Electrical Measurement
// { "A_0B04_0100", "DCVoltage", &Z_Copy, nullptr }, // Occupancy (map8)
// { "A_0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
// { "A_0B04_????", "", &Z_Remove, nullptr }, // Remove all other values
};
// ======================================================================
// Remove attribute
int32_t Z_Remove(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
return 1; // remove original key
}
// Temperature ZCL 4.4
key = F(ZCL_TEMPERATURE);
if (json.containsKey(key)) {
// parse temperature
int32_t temperature = json[key];
json.remove(key);
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
// Copy value as-is
int32_t Z_Copy(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
json[new_name] = value;
return 1; // remove original key
}
// Pressure ZCL 4.5
key = F(ZCL_PRESSURE);
if (json.containsKey(key)) {
json[F(D_JSON_PRESSURE)] = json[key];
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
json.remove(key);
}
json.remove(F(ZCL_PRESSURE_SCALE));
json.remove(F(ZCL_PRESSURE_SCALED));
// Humidity ZCL 4.7
key = F(ZCL_HUMIDITY);
if (json.containsKey(key)) {
// parse temperature
uint32_t humidity = json[key];
json.remove(key);
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
// Copy value as-is
int32_t Z_Const_Keep(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
json[new_name] = (char*)param;
return 0; // keep original key
}
// Osram Mini Switch
key = F(ZCL_OO_OFF);
if (json.containsKey(key)) {
json.remove(key);
json[F(D_CMND_POWER)] = F("Off");
}
key = F(ZCL_OO_ON);
if (json.containsKey(key)) {
json.remove(key);
json[F(D_CMND_POWER)] = F("On");
}
key = F(ZCL_COLORTEMP_MOVE);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint16_t color_temp = buf2.get16(0);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("ColorTemp")] = color_temp;
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_MOVE_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t level = buf2.get8(0);
uint16_t transition_time = buf2.get16(1);
json.remove(key);
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
json[F("TransitionTime")] = transition_time / 10.0f;
if (0 == level) {
json[F(D_CMND_POWER)] = F("Off");
} else {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_MOVE);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t level = buf2.get8(0);
uint16_t transition_time = buf2.get16(1);
json.remove(key);
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_MOVE_1);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t move_mode = buf2.get8(0);
uint8_t move_rate = buf2.get8(1);
json.remove(key);
json[F("Move")] = move_mode ? F("Down") : F("Up");
json[F("Rate")] = move_rate;
}
key = F(ZCL_LC_MOVE_1_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t move_mode = buf2.get8(0);
uint8_t move_rate = buf2.get8(1);
json.remove(key);
json[F("Move")] = move_mode ? F("Down") : F("Up");
json[F("Rate")] = move_rate;
if (0 == move_mode) {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_STEP);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t step_mode = buf2.get8(0);
uint8_t step_size = buf2.get8(1);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("Step")] = step_mode ? F("Down") : F("Up");
json[F("StepSize")] = step_size;
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_STEP_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t step_mode = buf2.get8(0);
uint8_t step_size = buf2.get8(1);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("Step")] = step_mode ? F("Down") : F("Up");
json[F("StepSize")] = step_size;
json[F("TransitionTime")] = transition_time / 10.0f;
if (0 == step_mode) {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_STOP);
if (json.containsKey(key)) {
json.remove(key);
json[F("Stop")] = 1;
}
key = F(ZCL_LC_STOP_WOO);
if (json.containsKey(key)) {
json.remove(key);
json[F("Stop")] = 1;
// Convert int to float with divider
int32_t Z_ConvFloatDivider(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
float f_value = value;
float *divider = (float*) param;
json[new_name] = f_value / *divider;
return 1; // remove original key
}
// Lumi.weather proprietary field
key = F(ZCL_LUMI_WEATHER);
if (json.containsKey(key)) {
String hex = json[key];
int32_t Z_AqaraSensor(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
DynamicJsonBuffer jsonBuffer;
JsonObject& json_lumi = jsonBuffer.createObject();
uint32_t i = 0;
uint32_t len = buf2.len();
char shortaddr[8];
char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
JsonVariant sub_value;
while (len - i >= 2) {
uint8_t attrid = buf2.get8(i++);
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
i += parseSingleAttribute(json_lumi, shortaddr, buf2, i, len);
}
// parse output
if (json_lumi.containsKey("0x64")) { // Temperature
int32_t temperature = json_lumi["0x64"];
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
}
if (json_lumi.containsKey("0x65")) { // Humidity
uint32_t humidity = json_lumi["0x65"];
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
}
if (json_lumi.containsKey("0x66")) { // Pressure
int32_t pressure = json_lumi["0x66"];
json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
i += parseSingleAttribute(json, tmp, buf2, i, len);
float val = json[tmp];
json.remove(tmp);
if (0x64 == attrid) {
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
} else if (0x65 == attrid) {
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
} else if (0x66 == attrid) {
json[F(D_JSON_PRESSURE)] = val / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
} else if (0x01 == attrid) {
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
json[F("Battery")] = toPercentageCR2032(val);
}
if (json_lumi.containsKey("0x01")) { // Battery Voltage
uint32_t voltage = json_lumi["0x01"];
json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
json[F("Battery")] = toPercentageCR2032(voltage);
}
return 1; // remove original key
}
// ======================================================================
#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_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_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
#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
// Cluster Specific commands
#define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
#define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
#define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
#define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
#define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
#define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
#define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
#define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
#define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
#define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
#define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
// inspired from https://github.com/torvalds/linux/blob/master/lib/glob.c
bool mini_glob_match(char const *pat, char const *str) {
for (;;) {
unsigned char c = *str++;
unsigned char d = *pat++;
switch (d) {
case '?': /* Wildcard: anything but nul */
if (c == '\0')
return false;
break;
case '\\':
d = *pat++;
/*FALLTHROUGH*/
default: /* Literal character */
if (c == d) {
if (d == '\0')
return true;
break;
}
return false; /* No point continuing */
}
}
}
void ZCLFrame::postProcessAttributes(JsonObject& json) {
// iterate on json elements
for (auto kv : json) {
String key = kv.key;
JsonVariant& value = kv.value;
// Iterate on filter
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
if (mini_glob_match(converter->filter, key.c_str())) {
int32_t drop = (*converter->func)(json, key.c_str(), value, converter->name, converter->param);
if (drop) {
json.remove(key);
}
}
}
}
}
//void ZCLFrame::postProcessAttributes2(JsonObject& json) {
// void postProcessAttributes2(JsonObject& json) {
// const __FlashStringHelper *key;
//
// // Osram Mini Switch
// key = F(ZCL_OO_OFF);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("Off");
// }
// key = F(ZCL_OO_ON);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("On");
// }
// key = F(ZCL_COLORTEMP_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint16_t color_temp = buf2.get16(0);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("ColorTemp")] = color_temp;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == level) {
// json[F(D_CMND_POWER)] = F("Off");
// } else {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_1);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// }
// key = F(ZCL_LC_MOVE_1_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// if (0 == move_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STEP);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_STEP_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == step_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STOP);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
// key = F(ZCL_LC_STOP_WOO);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
//
// // Lumi.weather proprietary field
// key = F(ZCL_LUMI_WEATHER);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// DynamicJsonBuffer jsonBuffer;
// JsonObject& json_lumi = jsonBuffer.createObject();
// uint32_t i = 0;
// uint32_t len = buf2.len();
// char shortaddr[8];
//
// while (len - i >= 2) {
// uint8_t attrid = buf2.get8(i++);
//
// snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
//
// //json[shortaddr] = parseSingleAttribute(json_lumi, buf2, i, len, nullptr, 0);
// }
// // parse output
// if (json_lumi.containsKey("0x64")) { // Temperature
// int32_t temperature = json_lumi["0x64"];
// json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
// }
// if (json_lumi.containsKey("0x65")) { // Humidity
// uint32_t humidity = json_lumi["0x65"];
// json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
// }
// if (json_lumi.containsKey("0x66")) { // Pressure
// int32_t pressure = json_lumi["0x66"];
// json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
// json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
// }
// if (json_lumi.containsKey("0x01")) { // Battery Voltage
// uint32_t voltage = json_lumi["0x01"];
// json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
// json[F("Battery")] = toPercentageCR2032(voltage);
// }
// json.remove(key);
// }
//
// }
#endif // USE_ZIGBEE

View File

@ -0,0 +1,169 @@
/*
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
#include <vector>
#include <map>
typedef struct Z_Device {
uint16_t shortaddr;
uint64_t longaddr; // 0x00 means unspecified
std::vector<uint8_t> endpoints;
std::vector<uint32_t> clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
std::vector<uint32_t> clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
} Z_Device;
std::map<uint16_t, Z_Device> zigbee_devices = {};
template < typename T>
bool findInVector(const std::vector<T> & vecOfElements, const T & element) {
// Find given element in vector
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
if (it != vecOfElements.end()) {
return true;
} else {
return false;
}
}
// insert an entry when it is known it is missing
void Z_InsertShortAddrEntry(uint16_t shortaddr, uint64_t longaddr) {
Z_Device device = { shortaddr, longaddr,
std::vector<uint8_t>(),
std::vector<uint32_t>(),
std::vector<uint32_t>() };
zigbee_devices[shortaddr] = device;
}
void Z_AddDeviceLongAddr(uint16_t shortaddr, uint64_t longaddr) {
// is the short address already recorded?
if (0 == zigbee_devices.count(shortaddr)) {
// No, add an entry
Z_InsertShortAddrEntry(shortaddr, longaddr);
} else {
// Yes, update the longaddr if necessary
Z_Device &device = zigbee_devices[shortaddr];
uint64_t prev_longaddr = device.longaddr;
if (prev_longaddr != longaddr) {
// new device, i.e. collision
device.longaddr = longaddr;
device.endpoints.clear();
device.clusters_in.clear();
device.clusters_out.clear();
}
}
}
void Z_AddDeviceEndpoint(uint16_t shortaddr, uint8_t endpoint) {
if (0 == zigbee_devices.count(shortaddr)) {
// No entry
Z_InsertShortAddrEntry(shortaddr, 0);
}
Z_Device &device = zigbee_devices[shortaddr];
if (!findInVector(device.endpoints, endpoint)) {
device.endpoints.push_back(endpoint);
}
}
void Z_AddDeviceCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) {
if (0 == zigbee_devices.count(shortaddr)) {
// No entry
Z_InsertShortAddrEntry(shortaddr, 0);
}
Z_Device &device = zigbee_devices[shortaddr];
if (!findInVector(device.endpoints, endpoint)) {
device.endpoints.push_back(endpoint);
}
uint32_t ep_cluster = (endpoint << 16) | cluster;
if (!out) {
if (!findInVector(device.clusters_in, ep_cluster)) {
device.clusters_in.push_back(ep_cluster);
}
} else { // out
if (!findInVector(device.clusters_out, ep_cluster)) {
device.clusters_out.push_back(ep_cluster);
}
}
}
String Z_DumpDevices(void) {
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
JsonObject& devices = json.createNestedObject(F("ZigbeeDevices"));
for (std::map<uint16_t, Z_Device>::iterator it = zigbee_devices.begin(); it != zigbee_devices.end(); ++it) {
uint16_t shortaddr = it->first;
Z_Device& device = it->second;
char hex[20];
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
JsonObject& dev = devices.createNestedObject(hex);
dev[F("ShortAddr")] = hex;
Uint64toHex(device.longaddr, hex, 64);
dev[F("IEEEAddr")] = hex;
JsonArray& dev_endpoints = dev.createNestedArray(F("Endpoints"));
for (std::vector<uint8_t>::iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
uint8_t endpoint = *ite;
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
dev_endpoints.add(hex);
}
JsonObject& dev_clusters_in = dev.createNestedObject(F("Clusters_in"));
for (std::vector<uint32_t>::iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) {
uint16_t cluster = *itc & 0xFFFF;
uint8_t endpoint = (*itc >> 16) & 0xFF;
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
if (!dev_clusters_in.containsKey(hex)) {
dev_clusters_in.createNestedArray(hex);
}
JsonArray &cluster_arr = dev_clusters_in[hex];
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
cluster_arr.add(hex);
}
JsonObject& dev_clusters_out = dev.createNestedObject(F("Clusters_out"));
for (std::vector<uint32_t>::iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) {
uint16_t cluster = *itc & 0xFFFF;
uint8_t endpoint = (*itc >> 16) & 0xFF;
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
if (!dev_clusters_out.containsKey(hex)) {
dev_clusters_out.createNestedArray(hex);
}
JsonArray &cluster_arr = dev_clusters_out[hex];
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
cluster_arr.add(hex);
}
}
String payload = "";
payload.reserve(200);
json.printTo(payload);
return payload;
}
#endif // USE_ZIGBEE

View File

@ -0,0 +1,650 @@
/*
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
// Status code used for ZigbeeStatus MQTT message
// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}}
const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working
const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration
const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot
const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its address
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working
typedef int32_t (*ZB_Func)(uint8_t value);
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf);
typedef union Zigbee_Instruction {
struct {
uint8_t i; // instruction
uint8_t d8; // 8 bits data
uint16_t d16; // 16 bits data
} i;
const void *p; // pointer
// const void *m; // for type checking only, message
// const ZB_Func f;
// const ZB_RecvMsgFunc fr;
} Zigbee_Instruction;
//
// Zigbee_Instruction z1 = { .i = {1,2,3}};
// Zigbee_Instruction z3 = { .p = nullptr };
typedef struct Zigbee_Instruction_Type {
uint8_t instr;
uint8_t data;
} Zigbee_Instruction_Type;
enum Zigbee_StateMachine_Instruction_Set {
// 2 bytes instructions
ZGB_INSTR_4_BYTES = 0,
ZGB_INSTR_NOOP = 0, // do nothing
ZGB_INSTR_LABEL, // define a label
ZGB_INSTR_GOTO, // goto label
ZGB_INSTR_ON_ERROR_GOTO, // goto label if error
ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout
ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms)
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
ZGB_INSTR_STOP, // stop state machine with optional error code
// 6 bytes instructions
ZGB_INSTR_8_BYTES = 0x80,
ZGB_INSTR_CALL = 0x80, // call a function
ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function
ZGB_INSTR_MQTT_STATUS, // send MQTT status string with code
ZGB_INSTR_SEND, // send a ZNP message
ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others
ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter
ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr
// 10 bytes instructions
ZGB_INSTR_12_BYTES = 0xF0,
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
};
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
#define ZI_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (x), 0x0000 } }, { .p = ((const void*)(m)) },
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
// Labels used in the State Machine -- internal only
const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds
// errors
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
struct ZigbeeStatus {
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
bool state_machine = false; // the state machine is running
bool state_waiting = false; // the state machine is waiting for external event or timeout
bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running
bool ready = false; // cc2530 initialization is complet, ready to operate
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
int16_t pc = 0; // program counter, -1 means abort
uint32_t next_timeout = 0; // millis for the next timeout
uint8_t *recv_filter = nullptr; // receive filter message
bool recv_until = false; // ignore all messages until the received frame fully matches
size_t recv_filter_len = 0;
ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
bool init_phase = true; // initialization phase, before accepting zigbee traffic
};
struct ZigbeeStatus zigbee;
SBuffer *zigbee_buffer = nullptr;
/*********************************************************************************************\
* State Machine
\*********************************************************************************************/
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
// Macro to define message to send and receive
#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x };
#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL))
// ZBS_* Zigbee Send
// ZBR_* Zigbee Recv
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware reset response
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version
// Check if ZNP_HAS_CONFIGURED is set
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155
// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */,
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
0x08 /* len */,
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
) // 6604002D08xxxxxxxxxxxxxxxx
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
0x04 /* len */,
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
) // 6604008404xxxxxxxx
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
0x10 /* len */,
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
0x01 /* len */, 0x00 ) // 660400630100
// commands to "format" the device
// Write configuration - write success
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write
// Factory reset
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102
// Write PAN ID
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx
// Write EXT PAN ID
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */,
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
) // 26052D086263151D004B1200
// Write Channel ID
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */,
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
/*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx
// Write Logical Type = 00 = coordinator
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100
// Write precfgkey
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
0x10 /* len */,
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D
// Write precfgkey enable
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100
// Write Security Mode
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
0x00 /* offset */, 0x20 /* len */,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000
// Write Z_ZDO Direct CB
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101
// NV Init ZNP Has Configured
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709
// Init succeeded
//ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, Z_Created ) // 610709 - NV Write
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT ) // 6107xx, Success if 610700 or 610709 - NV Write
// Write ZNP Has Configured
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900
// Z_ZDO:startupFromApp
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started
// GetDeviceInfo
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00
// IEEE Adr (8 bytes) = 6263151D004B1200
// Short Addr (2 bytes) = 0000
// Device Type (1 byte) = 07 (coord?)
// Device State (1 byte) = 09 (coordinator started)
// NumAssocDevices (1 byte) = 00
// Read Pan ID
//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000
// Z_ZDO:nodeDescReq
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200
// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00
ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582
// SrcAddr (2 bytes) 0000
// Status (1 byte) 00 Success
// NwkAddr (2 bytes) 0000
// LogicalType (1 byte) - 00 Coordinator
// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands
// MACCapabilityFlags (1 byte) - 8F ALL
// ManufacturerCode (2 bytes) - 0000
// MaxBufferSize (1 byte) - 50 NPDU
// MaxTransferSize (2 bytes) - A000 = 160
// ServerMask (2 bytes) - 0100 - Primary Trust Center
// MaxOutTransferSize (2 bytes) - A000 = 160
// DescriptorCapabilities (1 byte) - 00
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
// Z_ZDO:activeEpReq
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running
// Z_AF:register profile:104, ep:01
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
// Z_ZDO:mgmtPermitJoinReq
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00
0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00
0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000
// Filters for ZCL frames
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_LABEL(0)
ZI_NOOP()
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize
ZI_ON_ERROR_GOTO(50)
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
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(100)
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
ZI_WAIT(100)
ZI_SEND(ZBS_VERSION) // check ZNP software version
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version
ZI_SEND(ZBS_PAN) // check PAN ID
ZI_WAIT_RECV(1000, ZBR_PAN)
ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
ZI_SEND(ZBS_CHANN) // check CHANNEL
ZI_WAIT_RECV(1000, ZBR_CHANN)
ZI_SEND(ZBS_PFGK) // check PFGK
ZI_WAIT_RECV(1000, ZBR_PFGK)
ZI_SEND(ZBS_PFGKEN) // check PFGKEN
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok")
// all is good, we can start
ZI_LABEL(ZIGBEE_LABEL_START) // START ZNP App
ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
//ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
// Z_ZDO:startupFromApp
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator")
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
// Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO
// redo Z_ZDO:activeEpReq to check that Ep are available
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
//ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command
//ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
ZI_LABEL(ZIGBEE_LABEL_READY)
ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started")
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
ZI_WAIT_FOREVER()
ZI_GOTO(ZIGBEE_LABEL_READY)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(50) // reformat device
ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset")
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_SEND(ZBS_FACTRES) // factory reset
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_RESET) // reset device
ZI_WAIT_RECV(5000, ZBR_RESET)
ZI_SEND(ZBS_W_PAN) // write PAN ID
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB
ZI_WAIT_RECV(1000, ZBR_W_OK)
// Now mark the device as ready, writing 0x55 in memory slot 0x0F00
ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured")
ZI_GOTO(ZIGBEE_LABEL_START)
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
ZI_GOTO(ZIGBEE_LABEL_ABORT)
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
ZI_STOP(ZIGBEE_LABEL_ABORT)
};
uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words)
if (instr >= ZGB_INSTR_12_BYTES) {
return 3;
} else if (instr >= ZGB_INSTR_8_BYTES) {
return 2;
} else {
return 1;
}
}
void ZigbeeGotoLabel(uint8_t label) {
// look for the label scanning entire code
uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found
uint8_t cur_instr = 0;
uint8_t cur_d8 = 0;
uint8_t cur_instr_len = 1; // size of current instruction in words
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr);
if (ZGB_INSTR_LABEL == cur_instr) {
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i);
if (label == cur_d8) {
// label found, goto to this pc
zigbee.pc = i;
zigbee.state_machine = true;
zigbee.state_waiting = false;
return;
}
}
// get instruction length
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
}
// no label found, abort
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc);
if (ZIGBEE_LABEL_ABORT != label) {
// if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
} else {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
zigbee.state_machine = false;
zigbee.active = false;
}
}
void ZigbeeStateMachine_Run(void) {
uint8_t cur_instr = 0;
uint8_t cur_d8 = 0;
uint16_t cur_d16 = 0;
const void* cur_ptr1 = nullptr;
const void* cur_ptr2 = nullptr;
uint32_t now = millis();
if (zigbee.state_waiting) { // state machine is waiting for external event or timeout
// checking if timeout expired
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever
//AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc);
if (!zigbee.state_no_timeout) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto);
ZigbeeGotoLabel(zigbee.on_timeout_goto);
} else {
zigbee.state_waiting = false; // simply stop waiting
}
}
}
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
// reinit receive filters and functions (they only work for a single instruction)
zigbee.recv_filter = nullptr;
zigbee.recv_func = nullptr;
zigbee.recv_until = false;
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
zigbee.pc = -1;
}
if (zigbee.pc < 0) {
zigbee.state_machine = false;
return;
}
// load current instruction details
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
if (cur_instr >= ZGB_INSTR_8_BYTES) {
cur_instr_line++;
cur_ptr1 = cur_instr_line->p;
}
if (cur_instr >= ZGB_INSTR_12_BYTES) {
cur_instr_line++;
cur_ptr2 = cur_instr_line->p;
}
zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto
switch (cur_instr) {
case ZGB_INSTR_NOOP:
case ZGB_INSTR_LABEL: // do nothing
break;
case ZGB_INSTR_GOTO:
ZigbeeGotoLabel(cur_d8);
break;
case ZGB_INSTR_ON_ERROR_GOTO:
zigbee.on_error_goto = cur_d8;
break;
case ZGB_INSTR_ON_TIMEOUT_GOTO:
zigbee.on_timeout_goto = cur_d8;
break;
case ZGB_INSTR_WAIT:
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
break;
case ZGB_INSTR_WAIT_FOREVER:
zigbee.next_timeout = 0;
zigbee.state_waiting = true;
//zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
break;
case ZGB_INSTR_STOP:
zigbee.state_machine = false;
if (cur_d8) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8);
}
break;
case ZGB_INSTR_CALL:
if (cur_ptr1) {
uint32_t res;
res = (*((ZB_Func)cur_ptr1))(cur_d8);
if (res > 0) {
ZigbeeGotoLabel(res);
continue; // avoid incrementing PC after goto
} else if (res == 0) {
// do nothing
} else if (res == -1) {
// do nothing
} else {
ZigbeeGotoLabel(zigbee.on_error_goto);
continue;
}
}
break;
case ZGB_INSTR_LOG:
AddLog_P(cur_d8, (char*) cur_ptr1);
break;
case ZGB_INSTR_MQTT_STATUS:
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
cur_d8, (char*) cur_ptr1);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
XdrvRulesProcess();
break;
case ZGB_INSTR_SEND:
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
break;
case ZGB_INSTR_WAIT_UNTIL:
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV
case ZGB_INSTR_WAIT_RECV:
zigbee.recv_filter = (uint8_t *) cur_ptr1;
zigbee.recv_filter_len = cur_d8; // len
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
break;
case ZGB_ON_RECV_UNEXPECTED:
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
break;
case ZGB_INSTR_WAIT_RECV_CALL:
zigbee.recv_filter = (uint8_t *) cur_ptr1;
zigbee.recv_filter_len = cur_d8; // len
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
break;
}
}
}
#endif // USE_ZIGBEE

View File

@ -0,0 +1,372 @@
/*
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
// IEEE Adr (8 bytes) = 0x00124B001D156362
// Short Addr (2 bytes) = 0x0000
// Device Type (1 byte) = 0x07 (coord?)
// Device State (1 byte) = 0x09 (coordinator started)
// NumAssocDevices (1 byte) = 0x02
// List of devices: 0x8683, 0x9199
Z_IEEEAddress long_adr = buf.get64(3);
Z_ShortAddress short_adr = buf.get16(11);
uint8_t device_type = buf.get8(13);
uint8_t device_state = buf.get8(14);
uint8_t device_associated = buf.get8(15);
char hex[20];
Uint64toHex(long_adr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
",\"DeviceType\":%d,\"DeviceState\":%d"
",\"NumAssocDevices\":%d"),
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
device_associated);
if (device_associated > 0) {
uint idx = 16;
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
for (uint32_t i = 0; i < device_associated; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
idx += 2;
}
ResponseAppend_P(PSTR("]"));
}
ResponseJsonEnd(); // append '}'
ResponseJsonEnd(); // append '}'
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
XdrvRulesProcess();
return res;
}
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
// Check the status after NV Init "ZNP Has Configured"
// Good response should be 610700 or 610709 (Success or Created)
// We only filter the response on 6107 and check the code in this function
uint8_t status = buf.get8(2);
if ((0x00 == status) || (0x09 == status)) {
return 0; // Ok, continue
} else {
return -2; // Error
}
}
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
// check that the version is supported
// typical version for ZNP 1.2
// 61020200-02.06.03.D9143401.0200000000
// TranportRev = 02
// Product = 00
// MajorRel = 2
// MinorRel = 6
// MaintRel = 3
// Revision = 20190425 d (0x013414D9)
uint8_t major_rel = buf.get8(4);
uint8_t minor_rel = buf.get8(5);
uint8_t maint_rel = buf.get8(6);
uint32_t revision = buf.get32(7);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
",\"MaintRel\":%d,\"Revision\":%d}}"),
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
maint_rel, revision);
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
}
}
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
return true;
} else {
return false;
}
}
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
// we received a PermitJoin status change
uint8_t duration = buf.get8(2);
uint8_t status_code;
const char* message;
if (0xFF == duration) {
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX;
message = PSTR("Enable Pairing mode until next boot");
} else if (duration > 0) {
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60;
message = PSTR("Enable Pairing mode for %d seconds");
} else {
status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE;
message = PSTR("Disable Pairing mode");
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"Message\":\""),
status_code);
ResponseAppend_P(message, duration);
ResponseAppend_P(PSTR("\"}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
XdrvRulesProcess();
return -1;
}
// Send ACTIVE_EP_REQ to collect active endpoints for this address
void Z_SendActiveEpReq(uint16_t shortaddr) {
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
}
// Send ZDO_SIMPLE_DESC_REQ to get full list of supported Clusters for a specific endpoint
void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint) {
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, // 2504
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
endpoint };
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
}
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
// Received ZDO_NODE_DESC_RSP
Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t logicalType = buf.get8(7);
uint8_t apsFlags = buf.get8(8);
uint8_t MACCapabilityFlags = buf.get8(9);
uint16_t manufacturerCapabilities = buf.get16(10);
uint8_t maxBufferSize = buf.get8(12);
uint16_t maxInTransferSize = buf.get16(13);
uint16_t serverMask = buf.get16(15);
uint16_t maxOutTransferSize = buf.get16(17);
uint8_t descriptorCapabilities = buf.get8(19);
if (0 == status) {
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
if (deviceType > 3) { deviceType = 3; }
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
complexDescriptorAvailable ? "true" : "false"
);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
}
return -1;
}
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
// Received ZDO_ACTIVE_EP_RSP
Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t activeEpCount = buf.get8(7);
uint8_t* activeEpList = (uint8_t*) buf.charptr(8);
for (uint32_t i = 0; i < activeEpCount; i++) {
Z_AddDeviceEndpoint(nwkAddr, activeEpList[i]);
}
for (uint32_t i = 0; i < activeEpCount; i++) {
Z_SendSimpleDescReq(nwkAddr, activeEpList[i]);
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"ActiveEndpoints\":["),
ZIGBEE_STATUS_ACTIVE_EP);
for (uint32_t i = 0; i < activeEpCount; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]);
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
// Received ZDO_SIMPLE_DESC_RSP
Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t lenDescriptor = buf.get8(7);
uint8_t endpoint = buf.get8(8);
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(13); // 0 Version 1.00
uint8_t numInCluster = buf.get8(14);
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
if (0 == status) {
for (uint32_t i = 0; i < numInCluster; i++) {
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false);
}
for (uint32_t i = 0; i < numOutCluster; i++) {
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true);
}
// String dump = Z_DumpDevices();
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVerion\":%d"
"\"InClusters\":["),
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
profileId, deviceId, deviceVersion);
for (uint32_t i = 0; i < numInCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(15 + i*2));
}
ResponseAppend_P(PSTR("],\"OutClusters\":["));
for (uint32_t i = 0; i < numOutCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2));
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
}
return -1;
}
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
Z_ShortAddress srcAddr = buf.get16(2);
Z_ShortAddress nwkAddr = buf.get16(4);
Z_IEEEAddress ieeeAddr = buf.get64(6);
uint8_t capabilities = buf.get8(14);
Z_AddDeviceLongAddr(nwkAddr, ieeeAddr);
// String dump = Z_DumpDevices();
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
char hex[20];
Uint64toHex(ieeeAddr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
(capabilities & 0x04) ? "true" : "false",
(capabilities & 0x08) ? "true" : "false",
(capabilities & 0x40) ? "true" : "false"
);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
Z_SendActiveEpReq(nwkAddr);
return -1;
}
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4);
Z_ShortAddress srcaddr = buf.get16(6);
uint8_t srcendpoint = buf.get8(8);
uint8_t dstendpoint = buf.get8(9);
uint8_t wasbroadcast = buf.get8(10);
uint8_t linkquality = buf.get8(11);
uint8_t securityuse = buf.get8(12);
uint32_t timestamp = buf.get32(13);
uint8_t seqnumber = buf.get8(17);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json_root = jsonBuffer.createObject();
JsonObject& json = json_root.createNestedObject(shortaddr);
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseRawAttributes(json);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
}
zcl_received.postProcessAttributes(json);
String msg("");
msg.reserve(100);
json_root.printTo(msg);
Response_P(PSTR("%s"), msg.c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
// Default message handler for new messages
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
if (zigbee.init_phase) {
// if still during initialization phase, ignore any unexpected message
return -1; // ignore message
} else {
if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) {
return Z_ReceiveAfIncomingMessage(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) {
return Z_ReceiveEndDeviceAnnonce(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, ZBR_PERMITJOIN_AREQ_OPEN_XX)) {
return Z_ReceivePermitJoinStatus(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_NODEDESCRSP)) {
return Z_ReceiveNodeDesc(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_ACTIVEEPRSP)) {
return Z_ReceiveActiveEp(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_SIMPLEDESCRSP)) {
return Z_ReceiveSimpleDesc(res, buf);
}
return -1;
}
}
int32_t Z_State_Ready(uint8_t value) {
zigbee.init_phase = false; // initialization phase complete
return 0; // continue
}
#endif // USE_ZIGBEE

View File

@ -24,21 +24,6 @@
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
const uint8_t ZIGBEE_SOF = 0xFE;
// Status code used for ZigbeeStatus MQTT message
// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}}
const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working
const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration
const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot
const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its address
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working
//#define Z_USE_SOFTWARE_SERIAL
#ifdef Z_USE_SOFTWARE_SERIAL
@ -50,793 +35,11 @@ TasmotaSerial *ZigbeeSerial = nullptr;
#endif
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_DUMP;
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin };
typedef int32_t (*ZB_Func)(uint8_t value);
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf);
typedef union Zigbee_Instruction {
struct {
uint8_t i; // instruction
uint8_t d8; // 8 bits data
uint16_t d16; // 16 bits data
} i;
const void *p; // pointer
// const void *m; // for type checking only, message
// const ZB_Func f;
// const ZB_RecvMsgFunc fr;
} Zigbee_Instruction;
//
// Zigbee_Instruction z1 = { .i = {1,2,3}};
// Zigbee_Instruction z3 = { .p = nullptr };
typedef struct Zigbee_Instruction_Type {
uint8_t instr;
uint8_t data;
} Zigbee_Instruction_Type;
enum Zigbee_StateMachine_Instruction_Set {
// 2 bytes instructions
ZGB_INSTR_4_BYTES = 0,
ZGB_INSTR_NOOP = 0, // do nothing
ZGB_INSTR_LABEL, // define a label
ZGB_INSTR_GOTO, // goto label
ZGB_INSTR_ON_ERROR_GOTO, // goto label if error
ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout
ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms)
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
ZGB_INSTR_STOP, // stop state machine with optional error code
// 6 bytes instructions
ZGB_INSTR_8_BYTES = 0x80,
ZGB_INSTR_CALL = 0x80, // call a function
ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function
ZGB_INSTR_MQTT_STATUS, // send MQTT status string with code
ZGB_INSTR_SEND, // send a ZNP message
ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others
ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter
ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr
// 10 bytes instructions
ZGB_INSTR_12_BYTES = 0xF0,
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
};
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
#define ZI_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (x), 0x0000 } }, { .p = ((const void*)(m)) },
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
// Labels used in the State Machine -- internal only
const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds
// errors
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
struct ZigbeeStatus {
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
bool state_machine = false; // the state machine is running
bool state_waiting = false; // the state machine is waiting for external event or timeout
bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running
bool ready = false; // cc2530 initialization is complet, ready to operate
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
int16_t pc = 0; // program counter, -1 means abort
uint32_t next_timeout = 0; // millis for the next timeout
uint8_t *recv_filter = nullptr; // receive filter message
bool recv_until = false; // ignore all messages until the received frame fully matches
size_t recv_filter_len = 0;
ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
bool init_phase = true; // initialization phase, before accepting zigbee traffic
};
struct ZigbeeStatus zigbee;
SBuffer *zigbee_buffer = nullptr;
/*********************************************************************************************\
* State Machine
\*********************************************************************************************/
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
// Macro to define message to send and receive
#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x };
#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL))
// ZBS_* Zigbee Send
// ZBR_* Zigbee Recv
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware reset response
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version
// Check if ZNP_HAS_CONFIGURED is set
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155
// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */,
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
0x08 /* len */,
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
) // 6604002D08xxxxxxxxxxxxxxxx
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
0x04 /* len */,
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
) // 6604008404xxxxxxxx
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
0x10 /* len */,
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
0x01 /* len */, 0x00 ) // 660400630100
// commands to "format" the device
// Write configuration - write success
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write
// Factory reset
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102
// Write PAN ID
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx
// Write EXT PAN ID
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */,
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
) // 26052D086263151D004B1200
// Write Channel ID
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */,
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
/*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx
// Write Logical Type = 00 = coordinator
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100
// Write precfgkey
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
0x10 /* len */,
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D
// Write precfgkey enable
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100
// Write Security Mode
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
0x00 /* offset */, 0x20 /* len */,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000
// Write Z_ZDO Direct CB
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101
// NV Init ZNP Has Configured
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709
// Init succeeded
//ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, Z_Created ) // 610709 - NV Write
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT ) // 6107xx, Success if 610700 or 610709 - NV Write
// Write ZNP Has Configured
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900
// Z_ZDO:startupFromApp
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started
// GetDeviceInfo
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00
// IEEE Adr (8 bytes) = 6263151D004B1200
// Short Addr (2 bytes) = 0000
// Device Type (1 byte) = 07 (coord?)
// Device State (1 byte) = 09 (coordinator started)
// NumAssocDevices (1 byte) = 00
// Read Pan ID
//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000
// Z_ZDO:nodeDescReq
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200
// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00
ZBM(AREQ_ZDO_NODEDESCREQ, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582
// SrcAddr (2 bytes) 0000
// Status (1 byte) 00 Success
// NwkAddr (2 bytes) 0000
// LogicalType (1 byte) - 00 Coordinator
// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands
// MACCapabilityFlags (1 byte) - 8F ALL
// ManufacturerCode (2 bytes) - 0000
// MaxBufferSize (1 byte) - 50 NPDU
// MaxTransferSize (2 bytes) - A000 = 160
// ServerMask (2 bytes) - 0100 - Primary Trust Center
// MaxOutTransferSize (2 bytes) - A000 = 160
// DescriptorCapabilities (1 byte) - 00
// Z_ZDO:activeEpReq
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running
// Z_AF:register profile:104, ep:01
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
// Z_ZDO:mgmtPermitJoinReq
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00
0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00
0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */)
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000
// Filters for ZCL frames
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_LABEL(0)
ZI_NOOP()
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize
ZI_ON_ERROR_GOTO(50)
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
ZI_SEND(ZBS_VERSION) // check ZNP software version
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version
ZI_SEND(ZBS_PAN) // check PAN ID
ZI_WAIT_RECV(1000, ZBR_PAN)
ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
ZI_SEND(ZBS_CHANN) // check CHANNEL
ZI_WAIT_RECV(1000, ZBR_CHANN)
ZI_SEND(ZBS_PFGK) // check PFGK
ZI_WAIT_RECV(1000, ZBR_PFGK)
ZI_SEND(ZBS_PFGKEN) // check PFGKEN
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok")
// all is good, we can start
ZI_LABEL(ZIGBEE_LABEL_START) // START ZNP App
ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
//ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
// Z_ZDO:startupFromApp
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator")
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCREQ)
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
// Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO
// redo Z_ZDO:activeEpReq to check that Ep are available
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
//ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command
//ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX)
ZI_LABEL(ZIGBEE_LABEL_READY)
ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started")
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
ZI_WAIT_FOREVER()
ZI_GOTO(ZIGBEE_LABEL_READY)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(50) // reformat device
ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset")
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_SEND(ZBS_FACTRES) // factory reset
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_RESET) // reset device
ZI_WAIT_RECV(5000, ZBR_RESET)
ZI_SEND(ZBS_W_PAN) // write PAN ID
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB
ZI_WAIT_RECV(1000, ZBR_W_OK)
// Now mark the device as ready, writing 0x55 in memory slot 0x0F00
ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured")
ZI_GOTO(ZIGBEE_LABEL_START)
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
ZI_GOTO(ZIGBEE_LABEL_ABORT)
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
ZI_STOP(ZIGBEE_LABEL_ABORT)
};
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
// IEEE Adr (8 bytes) = 0x00124B001D156362
// Short Addr (2 bytes) = 0x0000
// Device Type (1 byte) = 0x07 (coord?)
// Device State (1 byte) = 0x09 (coordinator started)
// NumAssocDevices (1 byte) = 0x02
// List of devices: 0x8683, 0x9199
Z_IEEEAddress long_adr = buf.get64(3);
Z_ShortAddress short_adr = buf.get16(11);
uint8_t device_type = buf.get8(13);
uint8_t device_state = buf.get8(14);
uint8_t device_associated = buf.get8(15);
char hex[20];
Uint64toHex(long_adr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
",\"DeviceType\":%d,\"DeviceState\":%d"
",\"NumAssocDevices\":%d"),
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
device_associated);
if (device_associated > 0) {
uint idx = 16;
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
for (uint32_t i = 0; i < device_associated; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
idx += 2;
}
ResponseAppend_P(PSTR("]"));
}
ResponseJsonEnd(); // append '}'
ResponseJsonEnd(); // append '}'
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
XdrvRulesProcess();
return res;
}
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
// Check the status after NV Init "ZNP Has Configured"
// Good response should be 610700 or 610709 (Success or Created)
// We only filter the response on 6107 and check the code in this function
uint8_t status = buf.get8(2);
if ((0x00 == status) || (0x09 == status)) {
return 0; // Ok, continue
} else {
return -2; // Error
}
}
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
// check that the version is supported
// typical version for ZNP 1.2
// 61020200-02.06.03.D9143401.0200000000
// TranportRev = 02
// Product = 00
// MajorRel = 2
// MinorRel = 6
// MaintRel = 3
// Revision = 20190425 d (0x013414D9)
uint8_t major_rel = buf.get8(4);
uint8_t minor_rel = buf.get8(5);
uint8_t maint_rel = buf.get8(6);
uint32_t revision = buf.get32(7);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
",\"MaintRel\":%d,\"Revision\":%d}}"),
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
maint_rel, revision);
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
}
}
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
return true;
} else {
return false;
}
}
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
Z_ShortAddress srcAddr = buf.get16(2);
Z_ShortAddress nwkAddr = buf.get16(4);
Z_IEEEAddress ieeeAddr = buf.get64(6);
uint8_t capabilities = buf.get8(14);
char hex[20];
Uint64toHex(ieeeAddr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
(capabilities & 0x04) ? "true" : "false",
(capabilities & 0x08) ? "true" : "false",
(capabilities & 0x40) ? "true" : "false"
);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4);
Z_ShortAddress srcaddr = buf.get16(6);
uint8_t srcendpoint = buf.get8(8);
uint8_t dstendpoint = buf.get8(9);
uint8_t wasbroadcast = buf.get8(10);
uint8_t linkquality = buf.get8(11);
uint8_t securityuse = buf.get8(12);
uint32_t timestamp = buf.get32(13);
uint8_t seqnumber = buf.get8(17);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json_root = jsonBuffer.createObject();
JsonObject& json = json_root.createNestedObject(shortaddr);
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseRawAttributes(json);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
}
zcl_received.postProcessAttributes(json);
String msg("");
msg.reserve(100);
json_root.printTo(msg);
Response_P(PSTR("%s"), msg.c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
// Default message handler for new messages
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
if (zigbee.init_phase) {
// if still during initialization phase, ignore any unexpected message
return -1; // ignore message
} else {
if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) {
return Z_ReceiveAfIncomingMessage(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) {
return Z_ReceiveEndDeviceAnnonce(res, buf);
}
return -1;
}
}
int32_t Z_State_Ready(uint8_t value) {
zigbee.init_phase = false; // initialization phase complete
return 0; // continue
}
uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words)
if (instr >= ZGB_INSTR_12_BYTES) {
return 3;
} else if (instr >= ZGB_INSTR_8_BYTES) {
return 2;
} else {
return 1;
}
}
void ZigbeeGotoLabel(uint8_t label) {
// look for the label scanning entire code
uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found
uint8_t cur_instr = 0;
uint8_t cur_d8 = 0;
uint8_t cur_instr_len = 1; // size of current instruction in words
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr);
if (ZGB_INSTR_LABEL == cur_instr) {
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i);
if (label == cur_d8) {
// label found, goto to this pc
zigbee.pc = i;
zigbee.state_machine = true;
zigbee.state_waiting = false;
return;
}
}
// get instruction length
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
}
// no label found, abort
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc);
if (ZIGBEE_LABEL_ABORT != label) {
// if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
} else {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
zigbee.state_machine = false;
zigbee.active = false;
}
}
void ZigbeeStateMachine_Run(void) {
uint8_t cur_instr = 0;
uint8_t cur_d8 = 0;
uint16_t cur_d16 = 0;
const void* cur_ptr1 = nullptr;
const void* cur_ptr2 = nullptr;
uint32_t now = millis();
if (zigbee.state_waiting) { // state machine is waiting for external event or timeout
// checking if timeout expired
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever
//AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc);
if (!zigbee.state_no_timeout) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto);
ZigbeeGotoLabel(zigbee.on_timeout_goto);
} else {
zigbee.state_waiting = false; // simply stop waiting
}
}
}
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
// reinit receive filters and functions (they only work for a single instruction)
zigbee.recv_filter = nullptr;
zigbee.recv_func = nullptr;
zigbee.recv_until = false;
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
zigbee.pc = -1;
}
if (zigbee.pc < 0) {
zigbee.state_machine = false;
return;
}
// load current instruction details
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
if (cur_instr >= ZGB_INSTR_8_BYTES) {
cur_instr_line++;
cur_ptr1 = cur_instr_line->p;
}
if (cur_instr >= ZGB_INSTR_12_BYTES) {
cur_instr_line++;
cur_ptr2 = cur_instr_line->p;
}
zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto
switch (cur_instr) {
case ZGB_INSTR_NOOP:
case ZGB_INSTR_LABEL: // do nothing
break;
case ZGB_INSTR_GOTO:
ZigbeeGotoLabel(cur_d8);
break;
case ZGB_INSTR_ON_ERROR_GOTO:
zigbee.on_error_goto = cur_d8;
break;
case ZGB_INSTR_ON_TIMEOUT_GOTO:
zigbee.on_timeout_goto = cur_d8;
break;
case ZGB_INSTR_WAIT:
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
break;
case ZGB_INSTR_WAIT_FOREVER:
zigbee.next_timeout = 0;
zigbee.state_waiting = true;
//zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
break;
case ZGB_INSTR_STOP:
zigbee.state_machine = false;
if (cur_d8) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8);
}
break;
case ZGB_INSTR_CALL:
if (cur_ptr1) {
uint32_t res;
res = (*((ZB_Func)cur_ptr1))(cur_d8);
if (res > 0) {
ZigbeeGotoLabel(res);
continue; // avoid incrementing PC after goto
} else if (res == 0) {
// do nothing
} else if (res == -1) {
// do nothing
} else {
ZigbeeGotoLabel(zigbee.on_error_goto);
continue;
}
}
break;
case ZGB_INSTR_LOG:
AddLog_P(cur_d8, (char*) cur_ptr1);
break;
case ZGB_INSTR_MQTT_STATUS:
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
cur_d8, (char*) cur_ptr1);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
XdrvRulesProcess();
break;
case ZGB_INSTR_SEND:
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
break;
case ZGB_INSTR_WAIT_UNTIL:
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV
case ZGB_INSTR_WAIT_RECV:
zigbee.recv_filter = (uint8_t *) cur_ptr1;
zigbee.recv_filter_len = cur_d8; // len
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
break;
case ZGB_ON_RECV_UNEXPECTED:
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
break;
case ZGB_INSTR_WAIT_RECV_CALL:
zigbee.recv_filter = (uint8_t *) cur_ptr1;
zigbee.recv_filter_len = cur_d8; // len
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
zigbee.next_timeout = now + cur_d16;
zigbee.state_waiting = true;
break;
}
}
}
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeDump };
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
@ -1031,6 +234,13 @@ void ZigbeeInit(void)
* Commands
\*********************************************************************************************/
void CmndZigbeeDump(void) {
if (ZigbeeSerial) {
String dump = Z_DumpDevices();
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, dump.c_str());
}
}
void CmndZigbeeZNPSend(void)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
@ -1086,7 +296,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
XdrvRulesProcess();
}
// Allow or Deny pairing of new Zigbee devices
void CmndZigbeePermitJoin(void)
{
uint32_t payload = XdrvMailbox.payload;