mirror of https://github.com/arendst/Tasmota.git
Merge pull request #6511 from s-hadinger/zigbee_dump
Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command
This commit is contained in:
commit
dffe2cdfd4
|
@ -3,6 +3,7 @@
|
|||
* Change command Tariffx to allow time entries like 23 (hours), 1320 (minutes) or 23:00. NOTE: As this is development branch previous tariffs are lost! (#6488)
|
||||
* Remove support for define USE_DS18x20_LEGACY and legacy DS18x20 driver (#6486)
|
||||
* Add initial support for MQTT logging using command MqttLog <loglevel> (#6498)
|
||||
* Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command
|
||||
*
|
||||
* 6.6.0.13 20190922
|
||||
* Add command EnergyReset4 x,x to initialize total usage for two tarrifs
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
typedef struct Z_Device {
|
||||
uint16_t shortaddr;
|
||||
uint64_t longaddr; // 0x00 means unspecified
|
||||
std::vector<uint8_t> endpoints;
|
||||
std::vector<uint32_t> clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
|
||||
std::vector<uint32_t> clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
|
||||
} Z_Device;
|
||||
|
||||
std::map<uint16_t, Z_Device> zigbee_devices = {};
|
||||
|
||||
|
||||
template < typename T>
|
||||
bool findInVector(const std::vector<T> & vecOfElements, const T & element) {
|
||||
// Find given element in vector
|
||||
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
|
||||
|
||||
if (it != vecOfElements.end()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// insert an entry when it is known it is missing
|
||||
void Z_InsertShortAddrEntry(uint16_t shortaddr, uint64_t longaddr) {
|
||||
Z_Device device = { shortaddr, longaddr,
|
||||
std::vector<uint8_t>(),
|
||||
std::vector<uint32_t>(),
|
||||
std::vector<uint32_t>() };
|
||||
zigbee_devices[shortaddr] = device;
|
||||
}
|
||||
|
||||
void Z_AddDeviceLongAddr(uint16_t shortaddr, uint64_t longaddr) {
|
||||
// is the short address already recorded?
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No, add an entry
|
||||
Z_InsertShortAddrEntry(shortaddr, longaddr);
|
||||
} else {
|
||||
// Yes, update the longaddr if necessary
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
uint64_t prev_longaddr = device.longaddr;
|
||||
if (prev_longaddr != longaddr) {
|
||||
// new device, i.e. collision
|
||||
device.longaddr = longaddr;
|
||||
device.endpoints.clear();
|
||||
device.clusters_in.clear();
|
||||
device.clusters_out.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Z_AddDeviceEndpoint(uint16_t shortaddr, uint8_t endpoint) {
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No entry
|
||||
Z_InsertShortAddrEntry(shortaddr, 0);
|
||||
}
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
if (!findInVector(device.endpoints, endpoint)) {
|
||||
device.endpoints.push_back(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
void Z_AddDeviceCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) {
|
||||
if (0 == zigbee_devices.count(shortaddr)) {
|
||||
// No entry
|
||||
Z_InsertShortAddrEntry(shortaddr, 0);
|
||||
}
|
||||
Z_Device &device = zigbee_devices[shortaddr];
|
||||
if (!findInVector(device.endpoints, endpoint)) {
|
||||
device.endpoints.push_back(endpoint);
|
||||
}
|
||||
uint32_t ep_cluster = (endpoint << 16) | cluster;
|
||||
if (!out) {
|
||||
if (!findInVector(device.clusters_in, ep_cluster)) {
|
||||
device.clusters_in.push_back(ep_cluster);
|
||||
}
|
||||
} else { // out
|
||||
if (!findInVector(device.clusters_out, ep_cluster)) {
|
||||
device.clusters_out.push_back(ep_cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String Z_DumpDevices(void) {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
JsonObject& devices = json.createNestedObject(F("ZigbeeDevices"));
|
||||
|
||||
for (std::map<uint16_t, Z_Device>::iterator it = zigbee_devices.begin(); it != zigbee_devices.end(); ++it) {
|
||||
uint16_t shortaddr = it->first;
|
||||
Z_Device& device = it->second;
|
||||
char hex[20];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
JsonObject& dev = devices.createNestedObject(hex);
|
||||
dev[F("ShortAddr")] = hex;
|
||||
|
||||
Uint64toHex(device.longaddr, hex, 64);
|
||||
dev[F("IEEEAddr")] = hex;
|
||||
|
||||
JsonArray& dev_endpoints = dev.createNestedArray(F("Endpoints"));
|
||||
for (std::vector<uint8_t>::iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
|
||||
uint8_t endpoint = *ite;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
dev_endpoints.add(hex);
|
||||
}
|
||||
|
||||
JsonObject& dev_clusters_in = dev.createNestedObject(F("Clusters_in"));
|
||||
for (std::vector<uint32_t>::iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) {
|
||||
uint16_t cluster = *itc & 0xFFFF;
|
||||
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
if (!dev_clusters_in.containsKey(hex)) {
|
||||
dev_clusters_in.createNestedArray(hex);
|
||||
}
|
||||
JsonArray &cluster_arr = dev_clusters_in[hex];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
||||
cluster_arr.add(hex);
|
||||
}
|
||||
|
||||
JsonObject& dev_clusters_out = dev.createNestedObject(F("Clusters_out"));
|
||||
for (std::vector<uint32_t>::iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) {
|
||||
uint16_t cluster = *itc & 0xFFFF;
|
||||
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
||||
if (!dev_clusters_out.containsKey(hex)) {
|
||||
dev_clusters_out.createNestedArray(hex);
|
||||
}
|
||||
JsonArray &cluster_arr = dev_clusters_out[hex];
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
||||
cluster_arr.add(hex);
|
||||
}
|
||||
}
|
||||
String payload = "";
|
||||
payload.reserve(200);
|
||||
json.printTo(payload);
|
||||
return payload;
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
// Status code used for ZigbeeStatus MQTT message
|
||||
// Ex: {"ZigbeeStatus":{"Status": 3,"Message":"Configured, starting coordinator"}}
|
||||
const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working
|
||||
const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting
|
||||
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration
|
||||
const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds
|
||||
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot
|
||||
const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its address
|
||||
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
|
||||
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
|
||||
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
|
||||
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
|
||||
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
|
||||
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working
|
||||
|
||||
typedef int32_t (*ZB_Func)(uint8_t value);
|
||||
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf);
|
||||
|
||||
typedef union Zigbee_Instruction {
|
||||
struct {
|
||||
uint8_t i; // instruction
|
||||
uint8_t d8; // 8 bits data
|
||||
uint16_t d16; // 16 bits data
|
||||
} i;
|
||||
const void *p; // pointer
|
||||
// const void *m; // for type checking only, message
|
||||
// const ZB_Func f;
|
||||
// const ZB_RecvMsgFunc fr;
|
||||
} Zigbee_Instruction;
|
||||
//
|
||||
// Zigbee_Instruction z1 = { .i = {1,2,3}};
|
||||
// Zigbee_Instruction z3 = { .p = nullptr };
|
||||
|
||||
typedef struct Zigbee_Instruction_Type {
|
||||
uint8_t instr;
|
||||
uint8_t data;
|
||||
} Zigbee_Instruction_Type;
|
||||
|
||||
enum Zigbee_StateMachine_Instruction_Set {
|
||||
// 2 bytes instructions
|
||||
ZGB_INSTR_4_BYTES = 0,
|
||||
ZGB_INSTR_NOOP = 0, // do nothing
|
||||
ZGB_INSTR_LABEL, // define a label
|
||||
ZGB_INSTR_GOTO, // goto label
|
||||
ZGB_INSTR_ON_ERROR_GOTO, // goto label if error
|
||||
ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout
|
||||
ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms)
|
||||
ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active
|
||||
ZGB_INSTR_STOP, // stop state machine with optional error code
|
||||
|
||||
// 6 bytes instructions
|
||||
ZGB_INSTR_8_BYTES = 0x80,
|
||||
ZGB_INSTR_CALL = 0x80, // call a function
|
||||
ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function
|
||||
ZGB_INSTR_MQTT_STATUS, // send MQTT status string with code
|
||||
ZGB_INSTR_SEND, // send a ZNP message
|
||||
ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others
|
||||
ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter
|
||||
ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr
|
||||
|
||||
// 10 bytes instructions
|
||||
ZGB_INSTR_12_BYTES = 0xF0,
|
||||
ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive
|
||||
};
|
||||
|
||||
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
|
||||
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
|
||||
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
|
||||
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
|
||||
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
|
||||
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
|
||||
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
|
||||
|
||||
#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
||||
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
|
||||
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
||||
#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
|
||||
|
||||
// Labels used in the State Machine -- internal only
|
||||
const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP
|
||||
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
|
||||
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds
|
||||
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds
|
||||
// errors
|
||||
const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error
|
||||
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
|
||||
|
||||
struct ZigbeeStatus {
|
||||
bool active = true; // is Zigbee active for this device, i.e. GPIOs configured
|
||||
bool state_machine = false; // the state machine is running
|
||||
bool state_waiting = false; // the state machine is waiting for external event or timeout
|
||||
bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running
|
||||
bool ready = false; // cc2530 initialization is complet, ready to operate
|
||||
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort
|
||||
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort
|
||||
int16_t pc = 0; // program counter, -1 means abort
|
||||
uint32_t next_timeout = 0; // millis for the next timeout
|
||||
|
||||
uint8_t *recv_filter = nullptr; // receive filter message
|
||||
bool recv_until = false; // ignore all messages until the received frame fully matches
|
||||
size_t recv_filter_len = 0;
|
||||
ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected
|
||||
ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received
|
||||
|
||||
bool init_phase = true; // initialization phase, before accepting zigbee traffic
|
||||
};
|
||||
struct ZigbeeStatus zigbee;
|
||||
|
||||
SBuffer *zigbee_buffer = nullptr;
|
||||
|
||||
/*********************************************************************************************\
|
||||
* State Machine
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
|
||||
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
|
||||
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
|
||||
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
|
||||
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
|
||||
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
|
||||
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
|
||||
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
|
||||
// Macro to define message to send and receive
|
||||
#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x };
|
||||
|
||||
#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL))
|
||||
|
||||
// ZBS_* Zigbee Send
|
||||
// ZBR_* Zigbee Recv
|
||||
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset
|
||||
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware reset response
|
||||
|
||||
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version
|
||||
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version
|
||||
|
||||
// Check if ZNP_HAS_CONFIGURED is set
|
||||
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155
|
||||
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155
|
||||
// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */
|
||||
|
||||
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483
|
||||
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx
|
||||
|
||||
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D
|
||||
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
|
||||
0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
|
||||
) // 6604002D08xxxxxxxxxxxxxxxx
|
||||
|
||||
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484
|
||||
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
|
||||
0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
||||
) // 6604008404xxxxxxxx
|
||||
|
||||
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462
|
||||
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D
|
||||
|
||||
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463
|
||||
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
|
||||
0x01 /* len */, 0x00 ) // 660400630100
|
||||
|
||||
// commands to "format" the device
|
||||
// Write configuration - write success
|
||||
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration
|
||||
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write
|
||||
|
||||
// Factory reset
|
||||
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102
|
||||
// Write PAN ID
|
||||
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx
|
||||
// Write EXT PAN ID
|
||||
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */,
|
||||
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
||||
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
|
||||
) // 26052D086263151D004B1200
|
||||
// Write Channel ID
|
||||
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */,
|
||||
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
||||
/*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx
|
||||
// Write Logical Type = 00 = coordinator
|
||||
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100
|
||||
// Write precfgkey
|
||||
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
|
||||
0x10 /* len */,
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
||||
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
||||
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
||||
/*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D
|
||||
// Write precfgkey enable
|
||||
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100
|
||||
// Write Security Mode
|
||||
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
|
||||
0x00 /* offset */, 0x20 /* len */,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
|
||||
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000
|
||||
// Write Z_ZDO Direct CB
|
||||
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101
|
||||
// NV Init ZNP Has Configured
|
||||
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
|
||||
0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709
|
||||
// Init succeeded
|
||||
//ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, Z_Created ) // 610709 - NV Write
|
||||
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT ) // 6107xx, Success if 610700 or 610709 - NV Write
|
||||
|
||||
// Write ZNP Has Configured
|
||||
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
|
||||
0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900
|
||||
// Z_ZDO:startupFromApp
|
||||
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400
|
||||
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error
|
||||
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started
|
||||
// GetDeviceInfo
|
||||
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700
|
||||
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00
|
||||
// IEEE Adr (8 bytes) = 6263151D004B1200
|
||||
// Short Addr (2 bytes) = 0000
|
||||
// Device Type (1 byte) = 07 (coord?)
|
||||
// Device State (1 byte) = 09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 00
|
||||
|
||||
// Read Pan ID
|
||||
//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000
|
||||
|
||||
// Z_ZDO:nodeDescReq
|
||||
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000
|
||||
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200
|
||||
// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00
|
||||
ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582
|
||||
// SrcAddr (2 bytes) 0000
|
||||
// Status (1 byte) 00 Success
|
||||
// NwkAddr (2 bytes) 0000
|
||||
// LogicalType (1 byte) - 00 Coordinator
|
||||
// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands
|
||||
// MACCapabilityFlags (1 byte) - 8F ALL
|
||||
// ManufacturerCode (2 bytes) - 0000
|
||||
// MaxBufferSize (1 byte) - 50 NPDU
|
||||
// MaxTransferSize (2 bytes) - A000 = 160
|
||||
// ServerMask (2 bytes) - 0100 - Primary Trust Center
|
||||
// MaxOutTransferSize (2 bytes) - A000 = 160
|
||||
// DescriptorCapabilities (1 byte) - 00
|
||||
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
||||
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
|
||||
|
||||
// Z_ZDO:activeEpReq
|
||||
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running
|
||||
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success,
|
||||
0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running
|
||||
|
||||
// Z_AF:register profile:104, ep:01
|
||||
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000
|
||||
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */)
|
||||
// Z_ZDO:mgmtPermitJoinReq
|
||||
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
|
||||
0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00
|
||||
0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00
|
||||
0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */)
|
||||
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
|
||||
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000
|
||||
|
||||
// Filters for ZCL frames
|
||||
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
||||
ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
|
||||
|
||||
static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
||||
ZI_LABEL(0)
|
||||
ZI_NOOP()
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
|
||||
ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize
|
||||
ZI_ON_ERROR_GOTO(50)
|
||||
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device")
|
||||
ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s
|
||||
ZI_WAIT(100)
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration")
|
||||
ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured
|
||||
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
|
||||
ZI_WAIT(100)
|
||||
ZI_SEND(ZBS_VERSION) // check ZNP software version
|
||||
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version
|
||||
ZI_SEND(ZBS_PAN) // check PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_PAN)
|
||||
ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
|
||||
ZI_SEND(ZBS_CHANN) // check CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_CHANN)
|
||||
ZI_SEND(ZBS_PFGK) // check PFGK
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGK)
|
||||
ZI_SEND(ZBS_PFGKEN) // check PFGKEN
|
||||
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok")
|
||||
// all is good, we can start
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_START) // START ZNP App
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
|
||||
//ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
// Z_ZDO:startupFromApp
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator")
|
||||
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
|
||||
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
|
||||
ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started
|
||||
ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
|
||||
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
||||
//ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info
|
||||
ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
||||
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
|
||||
ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation
|
||||
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
||||
// Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO
|
||||
// redo Z_ZDO:activeEpReq to check that Ep are available
|
||||
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq
|
||||
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
//ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command
|
||||
//ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_READY)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started")
|
||||
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
|
||||
ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages
|
||||
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
ZI_WAIT_FOREVER()
|
||||
ZI_GOTO(ZIGBEE_LABEL_READY)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
|
||||
//ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
|
||||
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
|
||||
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
||||
//ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful
|
||||
//ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_FF)
|
||||
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
||||
|
||||
ZI_LABEL(50) // reformat device
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset")
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_SEND(ZBS_FACTRES) // factory reset
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_RESET) // reset device
|
||||
ZI_WAIT_RECV(5000, ZBR_RESET)
|
||||
ZI_SEND(ZBS_W_PAN) // write PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB
|
||||
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
||||
// Now mark the device as ready, writing 0x55 in memory slot 0x0F00
|
||||
ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
|
||||
ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
|
||||
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
|
||||
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
|
||||
//ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured")
|
||||
ZI_GOTO(ZIGBEE_LABEL_START)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
|
||||
ZI_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
|
||||
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
|
||||
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
|
||||
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
|
||||
ZI_STOP(ZIGBEE_LABEL_ABORT)
|
||||
};
|
||||
|
||||
uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words)
|
||||
if (instr >= ZGB_INSTR_12_BYTES) {
|
||||
return 3;
|
||||
} else if (instr >= ZGB_INSTR_8_BYTES) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeGotoLabel(uint8_t label) {
|
||||
// look for the label scanning entire code
|
||||
uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint8_t cur_instr_len = 1; // size of current instruction in words
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr);
|
||||
|
||||
if (ZGB_INSTR_LABEL == cur_instr) {
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i);
|
||||
if (label == cur_d8) {
|
||||
// label found, goto to this pc
|
||||
zigbee.pc = i;
|
||||
zigbee.state_machine = true;
|
||||
zigbee.state_waiting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// get instruction length
|
||||
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
|
||||
}
|
||||
|
||||
// no label found, abort
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc);
|
||||
if (ZIGBEE_LABEL_ABORT != label) {
|
||||
// if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT
|
||||
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
|
||||
} else {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
|
||||
zigbee.state_machine = false;
|
||||
zigbee.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeStateMachine_Run(void) {
|
||||
uint8_t cur_instr = 0;
|
||||
uint8_t cur_d8 = 0;
|
||||
uint16_t cur_d16 = 0;
|
||||
const void* cur_ptr1 = nullptr;
|
||||
const void* cur_ptr2 = nullptr;
|
||||
uint32_t now = millis();
|
||||
|
||||
if (zigbee.state_waiting) { // state machine is waiting for external event or timeout
|
||||
// checking if timeout expired
|
||||
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc);
|
||||
if (!zigbee.state_no_timeout) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto);
|
||||
ZigbeeGotoLabel(zigbee.on_timeout_goto);
|
||||
} else {
|
||||
zigbee.state_waiting = false; // simply stop waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
|
||||
// reinit receive filters and functions (they only work for a single instruction)
|
||||
zigbee.recv_filter = nullptr;
|
||||
zigbee.recv_func = nullptr;
|
||||
zigbee.recv_until = false;
|
||||
zigbee.state_no_timeout = false; // reset the no_timeout for next instruction
|
||||
|
||||
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
|
||||
zigbee.pc = -1;
|
||||
}
|
||||
if (zigbee.pc < 0) {
|
||||
zigbee.state_machine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// load current instruction details
|
||||
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
|
||||
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
|
||||
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
||||
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
||||
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
|
||||
if (cur_instr >= ZGB_INSTR_8_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr1 = cur_instr_line->p;
|
||||
}
|
||||
if (cur_instr >= ZGB_INSTR_12_BYTES) {
|
||||
cur_instr_line++;
|
||||
cur_ptr2 = cur_instr_line->p;
|
||||
}
|
||||
|
||||
zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto
|
||||
|
||||
switch (cur_instr) {
|
||||
case ZGB_INSTR_NOOP:
|
||||
case ZGB_INSTR_LABEL: // do nothing
|
||||
break;
|
||||
case ZGB_INSTR_GOTO:
|
||||
ZigbeeGotoLabel(cur_d8);
|
||||
break;
|
||||
case ZGB_INSTR_ON_ERROR_GOTO:
|
||||
zigbee.on_error_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_ON_TIMEOUT_GOTO:
|
||||
zigbee.on_timeout_goto = cur_d8;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT:
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_FOREVER:
|
||||
zigbee.next_timeout = 0;
|
||||
zigbee.state_waiting = true;
|
||||
//zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done
|
||||
break;
|
||||
case ZGB_INSTR_STOP:
|
||||
zigbee.state_machine = false;
|
||||
if (cur_d8) {
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8);
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_CALL:
|
||||
if (cur_ptr1) {
|
||||
uint32_t res;
|
||||
res = (*((ZB_Func)cur_ptr1))(cur_d8);
|
||||
if (res > 0) {
|
||||
ZigbeeGotoLabel(res);
|
||||
continue; // avoid incrementing PC after goto
|
||||
} else if (res == 0) {
|
||||
// do nothing
|
||||
} else if (res == -1) {
|
||||
// do nothing
|
||||
} else {
|
||||
ZigbeeGotoLabel(zigbee.on_error_goto);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ZGB_INSTR_LOG:
|
||||
AddLog_P(cur_d8, (char*) cur_ptr1);
|
||||
break;
|
||||
case ZGB_INSTR_MQTT_STATUS:
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
|
||||
cur_d8, (char*) cur_ptr1);
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
break;
|
||||
case ZGB_INSTR_SEND:
|
||||
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */);
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_UNTIL:
|
||||
zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV
|
||||
case ZGB_INSTR_WAIT_RECV:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
case ZGB_ON_RECV_UNEXPECTED:
|
||||
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
|
||||
break;
|
||||
case ZGB_INSTR_WAIT_RECV_CALL:
|
||||
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
||||
zigbee.recv_filter_len = cur_d8; // len
|
||||
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
|
||||
zigbee.next_timeout = now + cur_d16;
|
||||
zigbee.state_waiting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
|
||||
|
||||
Copyright (C) 2019 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
|
||||
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
|
||||
// IEEE Adr (8 bytes) = 0x00124B001D156362
|
||||
// Short Addr (2 bytes) = 0x0000
|
||||
// Device Type (1 byte) = 0x07 (coord?)
|
||||
// Device State (1 byte) = 0x09 (coordinator started)
|
||||
// NumAssocDevices (1 byte) = 0x02
|
||||
// List of devices: 0x8683, 0x9199
|
||||
Z_IEEEAddress long_adr = buf.get64(3);
|
||||
Z_ShortAddress short_adr = buf.get16(11);
|
||||
uint8_t device_type = buf.get8(13);
|
||||
uint8_t device_state = buf.get8(14);
|
||||
uint8_t device_associated = buf.get8(15);
|
||||
|
||||
char hex[20];
|
||||
Uint64toHex(long_adr, hex, 64);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||
",\"DeviceType\":%d,\"DeviceState\":%d"
|
||||
",\"NumAssocDevices\":%d"),
|
||||
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
|
||||
device_associated);
|
||||
|
||||
if (device_associated > 0) {
|
||||
uint idx = 16;
|
||||
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
|
||||
for (uint32_t i = 0; i < device_associated; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
|
||||
idx += 2;
|
||||
}
|
||||
ResponseAppend_P(PSTR("]"));
|
||||
}
|
||||
|
||||
ResponseJsonEnd(); // append '}'
|
||||
ResponseJsonEnd(); // append '}'
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
|
||||
// Check the status after NV Init "ZNP Has Configured"
|
||||
// Good response should be 610700 or 610709 (Success or Created)
|
||||
// We only filter the response on 6107 and check the code in this function
|
||||
uint8_t status = buf.get8(2);
|
||||
if ((0x00 == status) || (0x09 == status)) {
|
||||
return 0; // Ok, continue
|
||||
} else {
|
||||
return -2; // Error
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
||||
// check that the version is supported
|
||||
// typical version for ZNP 1.2
|
||||
// 61020200-02.06.03.D9143401.0200000000
|
||||
// TranportRev = 02
|
||||
// Product = 00
|
||||
// MajorRel = 2
|
||||
// MinorRel = 6
|
||||
// MaintRel = 3
|
||||
// Revision = 20190425 d (0x013414D9)
|
||||
uint8_t major_rel = buf.get8(4);
|
||||
uint8_t minor_rel = buf.get8(5);
|
||||
uint8_t maint_rel = buf.get8(6);
|
||||
uint32_t revision = buf.get32(7);
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
|
||||
",\"MaintRel\":%d,\"Revision\":%d}}"),
|
||||
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
|
||||
maint_rel, revision);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
|
||||
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
||||
return 0; // version 2.6.x is ok
|
||||
} else {
|
||||
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
||||
}
|
||||
}
|
||||
|
||||
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
|
||||
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
|
||||
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
||||
// we received a PermitJoin status change
|
||||
uint8_t duration = buf.get8(2);
|
||||
uint8_t status_code;
|
||||
const char* message;
|
||||
|
||||
if (0xFF == duration) {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX;
|
||||
message = PSTR("Enable Pairing mode until next boot");
|
||||
} else if (duration > 0) {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60;
|
||||
message = PSTR("Enable Pairing mode for %d seconds");
|
||||
} else {
|
||||
status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE;
|
||||
message = PSTR("Disable Pairing mode");
|
||||
}
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"Message\":\""),
|
||||
status_code);
|
||||
ResponseAppend_P(message, duration);
|
||||
ResponseAppend_P(PSTR("\"}}"));
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send ACTIVE_EP_REQ to collect active endpoints for this address
|
||||
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
||||
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
||||
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
|
||||
}
|
||||
|
||||
// Send ZDO_SIMPLE_DESC_REQ to get full list of supported Clusters for a specific endpoint
|
||||
void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint) {
|
||||
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, // 2504
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
|
||||
endpoint };
|
||||
|
||||
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
|
||||
}
|
||||
|
||||
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
|
||||
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_NODE_DESC_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t logicalType = buf.get8(7);
|
||||
uint8_t apsFlags = buf.get8(8);
|
||||
uint8_t MACCapabilityFlags = buf.get8(9);
|
||||
uint16_t manufacturerCapabilities = buf.get16(10);
|
||||
uint8_t maxBufferSize = buf.get8(12);
|
||||
uint16_t maxInTransferSize = buf.get16(13);
|
||||
uint16_t serverMask = buf.get16(15);
|
||||
uint16_t maxOutTransferSize = buf.get16(17);
|
||||
uint8_t descriptorCapabilities = buf.get8(19);
|
||||
|
||||
if (0 == status) {
|
||||
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
|
||||
if (deviceType > 3) { deviceType = 3; }
|
||||
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
|
||||
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
|
||||
complexDescriptorAvailable ? "true" : "false"
|
||||
);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_ACTIVE_EP_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t activeEpCount = buf.get8(7);
|
||||
uint8_t* activeEpList = (uint8_t*) buf.charptr(8);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
Z_AddDeviceEndpoint(nwkAddr, activeEpList[i]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
Z_SendSimpleDescReq(nwkAddr, activeEpList[i]);
|
||||
}
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"ActiveEndpoints\":["),
|
||||
ZIGBEE_STATUS_ACTIVE_EP);
|
||||
for (uint32_t i = 0; i < activeEpCount; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]);
|
||||
}
|
||||
ResponseAppend_P(PSTR("]}}"));
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
||||
// Received ZDO_SIMPLE_DESC_RSP
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
uint8_t status = buf.get8(4);
|
||||
Z_ShortAddress nwkAddr = buf.get16(5);
|
||||
uint8_t lenDescriptor = buf.get8(7);
|
||||
uint8_t endpoint = buf.get8(8);
|
||||
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
|
||||
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
|
||||
uint8_t deviceVersion = buf.get8(13); // 0 – Version 1.00
|
||||
uint8_t numInCluster = buf.get8(14);
|
||||
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
|
||||
|
||||
if (0 == status) {
|
||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false);
|
||||
}
|
||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||
Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true);
|
||||
}
|
||||
// String dump = Z_DumpDevices();
|
||||
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
|
||||
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVerion\":%d"
|
||||
"\"InClusters\":["),
|
||||
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
|
||||
profileId, deviceId, deviceVersion);
|
||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(15 + i*2));
|
||||
}
|
||||
ResponseAppend_P(PSTR("],\"OutClusters\":["));
|
||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2));
|
||||
}
|
||||
ResponseAppend_P(PSTR("]}}"));
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
||||
Z_ShortAddress srcAddr = buf.get16(2);
|
||||
Z_ShortAddress nwkAddr = buf.get16(4);
|
||||
Z_IEEEAddress ieeeAddr = buf.get64(6);
|
||||
uint8_t capabilities = buf.get8(14);
|
||||
|
||||
Z_AddDeviceLongAddr(nwkAddr, ieeeAddr);
|
||||
// String dump = Z_DumpDevices();
|
||||
// Serial.printf(">>> Devices dump = %s\n", dump.c_str());
|
||||
|
||||
char hex[20];
|
||||
Uint64toHex(ieeeAddr, hex, 64);
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
|
||||
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
||||
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
|
||||
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
|
||||
(capabilities & 0x04) ? "true" : "false",
|
||||
(capabilities & 0x08) ? "true" : "false",
|
||||
(capabilities & 0x40) ? "true" : "false"
|
||||
);
|
||||
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
Z_SendActiveEpReq(nwkAddr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
||||
uint16_t groupid = buf.get16(2);
|
||||
uint16_t clusterid = buf.get16(4);
|
||||
Z_ShortAddress srcaddr = buf.get16(6);
|
||||
uint8_t srcendpoint = buf.get8(8);
|
||||
uint8_t dstendpoint = buf.get8(9);
|
||||
uint8_t wasbroadcast = buf.get8(10);
|
||||
uint8_t linkquality = buf.get8(11);
|
||||
uint8_t securityuse = buf.get8(12);
|
||||
uint32_t timestamp = buf.get32(13);
|
||||
uint8_t seqnumber = buf.get8(17);
|
||||
|
||||
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
|
||||
|
||||
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
|
||||
srcendpoint, dstendpoint, wasbroadcast,
|
||||
linkquality, securityuse, seqnumber,
|
||||
timestamp);
|
||||
|
||||
char shortaddr[8];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_root = jsonBuffer.createObject();
|
||||
JsonObject& json = json_root.createNestedObject(shortaddr);
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseRawAttributes(json);
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
zcl_received.parseClusterSpecificCommand(json);
|
||||
}
|
||||
zcl_received.postProcessAttributes(json);
|
||||
|
||||
String msg("");
|
||||
msg.reserve(100);
|
||||
json_root.printTo(msg);
|
||||
|
||||
Response_P(PSTR("%s"), msg.c_str());
|
||||
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
|
||||
XdrvRulesProcess();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
||||
// Default message handler for new messages
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
|
||||
if (zigbee.init_phase) {
|
||||
// if still during initialization phase, ignore any unexpected message
|
||||
return -1; // ignore message
|
||||
} else {
|
||||
if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) {
|
||||
return Z_ReceiveAfIncomingMessage(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) {
|
||||
return Z_ReceiveEndDeviceAnnonce(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, ZBR_PERMITJOIN_AREQ_OPEN_XX)) {
|
||||
return Z_ReceivePermitJoinStatus(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_NODEDESCRSP)) {
|
||||
return Z_ReceiveNodeDesc(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_ACTIVEEPRSP)) {
|
||||
return Z_ReceiveActiveEp(res, buf);
|
||||
} else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_SIMPLEDESCRSP)) {
|
||||
return Z_ReceiveSimpleDesc(res, buf);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Z_State_Ready(uint8_t value) {
|
||||
zigbee.init_phase = false; // initialization phase complete
|
||||
return 0; // continue
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue