diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index e6fb28658..2da2d46ff 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -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 (#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 diff --git a/sonoff/i18n.h b/sonoff/i18n.h index 46bc52ea0..5dae197a6 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -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" diff --git a/sonoff/xdrv_23_zigbee_4_converters.ino b/sonoff/xdrv_23_zigbee_4_converters.ino index 2271c4371..09b2872b9 100644 --- a/sonoff/xdrv_23_zigbee_4_converters.ino +++ b/sonoff/xdrv_23_zigbee_4_converters.ino @@ -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 - // 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; - } + { "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 - // 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)); + { "A_0402_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature + { "A_0402_????", nullptr, &Z_Remove, nullptr }, // Remove all other values - // 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; - } + { "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 - // 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; - } + { "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 - // 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]; + { "A_0405_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity + { "A_0405_????", nullptr, &Z_Remove, nullptr }, // Remove all other values - while (len - i >= 2) { - uint8_t attrid = buf2.get8(i++); + { "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 - 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; - 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); - } + // 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 } +// 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 +} + +// 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 +} + +// 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 +} + +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()); + 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++); + + 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); + } + } + 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 diff --git a/sonoff/xdrv_23_zigbee_6_devices.ino b/sonoff/xdrv_23_zigbee_6_devices.ino new file mode 100644 index 000000000..128f11857 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_6_devices.ino @@ -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 . +*/ + +#ifdef USE_ZIGBEE + +#include +#include + +typedef struct Z_Device { + uint16_t shortaddr; + uint64_t longaddr; // 0x00 means unspecified + std::vector endpoints; + std::vector clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number + std::vector clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number +} Z_Device; + +std::map zigbee_devices = {}; + + +template < typename T> +bool findInVector(const std::vector & 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(), + std::vector(), + std::vector() }; + 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::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::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::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::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 diff --git a/sonoff/xdrv_23_zigbee_7_statemachine.ino b/sonoff/xdrv_23_zigbee_7_statemachine.ino new file mode 100644 index 000000000..6d271f0a9 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_7_statemachine.ino @@ -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 . +*/ + +#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 diff --git a/sonoff/xdrv_23_zigbee_8_parsers.ino b/sonoff/xdrv_23_zigbee_8_parsers.ino new file mode 100644 index 000000000..2a304f8f1 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_8_parsers.ino @@ -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 . +*/ + +#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 diff --git a/sonoff/xdrv_23_zigbee_9_impl.ino b/sonoff/xdrv_23_zigbee_9_impl.ino index 14251e04a..d570573c2 100644 --- a/sonoff/xdrv_23_zigbee_9_impl.ino +++ b/sonoff/xdrv_23_zigbee_9_impl.ino @@ -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;