// sntp_servermode_dhcp()
+//#endif // ESP8266
+
void WifiConnect(void)
{
if (!Settings->flag4.network_wifi) { return; }
WifiSetState(0);
WifiSetOutputPower();
+
+//#ifdef ESP8266
+ // https://github.com/arendst/Tasmota/issues/16061#issuecomment-1216970170
+// sntp_servermode_dhcp(0);
+//#endif // ESP8266
+
WiFi.persistent(false); // Solve possible wifi init errors
Wifi.status = 0;
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + (ESP_getChipId() & 0xF); // Add extra delay to stop overrun by simultanous re-connects
@@ -807,6 +817,14 @@ void wifiKeepAlive(void) {
#endif // ESP8266
bool WifiHostByName(const char* aHostname, IPAddress& aResult) {
+#ifdef ESP8266
+ if (WiFi.hostByName(aHostname, aResult, Settings->dns_timeout)) {
+ // Host name resolved
+ if (0xFFFFFFFF != (uint32_t)aResult) {
+ return true;
+ }
+ }
+#else
// DnsClient can't do one-shot mDNS queries so use WiFi.hostByName() for *.local
size_t hostname_len = strlen(aHostname);
if (strstr_P(aHostname, PSTR(".local")) == &aHostname[hostname_len] - 6) {
@@ -817,13 +835,14 @@ bool WifiHostByName(const char* aHostname, IPAddress& aResult) {
}
}
} else {
- // Use this instead of WiFi.hostByName or connect(host_name,.. to block less if DNS server is not found
+ // Use this instead of WiFi.hostByName or connect(host_name,.. to block less if DNS server is not found
uint32_t dns_address = (!TasmotaGlobal.global_state.eth_down) ? Settings->eth_ipv4_address[3] : Settings->ipv4_address[3];
DnsClient.begin((IPAddress)dns_address);
if (1 == DnsClient.getHostByName(aHostname, aResult)) {
return true;
}
}
+#endif
AddLog(LOG_LEVEL_DEBUG, PSTR("DNS: Unable to resolve '%s'"), aHostname);
return false;
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
index c3eff8f5d..1b454348f 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
@@ -2334,7 +2334,7 @@ void HandleInformation(void)
// }2 =
WSContentSend_P(HTTP_SCRIPT_INFO_BEGIN);
WSContentSend_P(PSTR(""));
- WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s"), TasmotaGlobal.version, TasmotaGlobal.image_name);
+ WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s%s"), TasmotaGlobal.version, TasmotaGlobal.image_name, GetCodeCores().c_str());
WSContentSend_P(PSTR("}1" D_BUILD_DATE_AND_TIME "}2%s"), GetBuildDateAndTime().c_str());
WSContentSend_P(PSTR("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_CORE_RELEASE "/%s"), ESP.getSdkVersion());
WSContentSend_P(PSTR("}1" D_UPTIME "}2%s"), GetUptime().c_str());
@@ -2444,9 +2444,15 @@ void HandleInformation(void)
WSContentSend_P(PSTR("}1}2 ")); // Empty line
WSContentSend_P(PSTR("}1" D_ESP_CHIP_ID "}2%d (%s)"), ESP_getChipId(), GetDeviceHardwareRevision().c_str());
- WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X"), ESP_getFlashChipId());
- WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d KB"), ESP_getFlashChipRealSize() / 1024);
- WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d KB"), ESP.getFlashChipSize() / 1024);
+ WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X (%s)"), ESP_getFlashChipId(), ESP_getFlashChipMode().c_str());
+#ifdef ESP32
+ WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d KB"), ESP.getFlashChipSize() / 1024);
+ WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d KB"), ESP_getFlashChipMagicSize() / 1024);
+#endif // ESP32
+#ifdef ESP8266
+ WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d KB"), ESP.getFlashChipRealSize() / 1024);
+ WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d KB"), ESP_getFlashChipSize() / 1024);
+#endif // ESP8266
WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%d KB"), ESP_getSketchSize() / 1024);
WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%d KB"), ESP_getFreeSketchSpace() / 1024);
#ifdef ESP32
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino b/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino
index 6867454ca..1d328d447 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino
@@ -66,7 +66,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|"
D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|"
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|"
- D_CMND_SENSORRETAIN "|" D_CMND_INFORETAIN "|" D_CMND_STATERETAIN
+ D_CMND_SENSORRETAIN "|" D_CMND_INFORETAIN "|" D_CMND_STATERETAIN "|" D_CMND_STATUSRETAIN
#endif // FIRMWARE_MINIMAL_ONLY
;
@@ -92,8 +92,8 @@ void (* const MqttCommand[])(void) PROGMEM = {
#endif // USE_MQTT_FILE
&CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient,
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog,
- &CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain,
- &CmndInfoRetain, &CmndStateRetain
+ &CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain,
+ &CmndSensorRetain, &CmndInfoRetain, &CmndStateRetain, &CmndStatusRetain
#endif // FIRMWARE_MINIMAL_ONLY
};
@@ -942,8 +942,8 @@ void MqttConnected(void) {
if (Mqtt.initial_connection_state) {
if (ResetReason() != REASON_DEEP_SLEEP_AWAKE) {
char stopic2[TOPSZ];
- Response_P(PSTR("{\"Info1\":{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}}"),
- ModuleName().c_str(), TasmotaGlobal.version, TasmotaGlobal.image_name, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, "", SET_MQTT_GRP_TOPIC));
+ Response_P(PSTR("{\"Info1\":{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}}"),
+ ModuleName().c_str(), TasmotaGlobal.version, TasmotaGlobal.image_name, GetCodeCores().c_str(), GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, "", SET_MQTT_GRP_TOPIC));
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_INFO "1"), Settings->flag5.mqtt_info_retain);
#ifdef USE_WEBSERVER
if (Settings->webserver) {
@@ -1597,24 +1597,24 @@ void CmndButtonRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
for (uint32_t i = 1; i <= MAX_KEYS; i++) {
- SendKey(KEY_BUTTON, i, CLEAR_RETAIN); // Clear MQTT retain in broker
+ SendKey(KEY_BUTTON, i, CLEAR_RETAIN); // Clear MQTT retain in broker
}
}
- Settings->flag.mqtt_button_retain = XdrvMailbox.payload; // CMND_BUTTONRETAIN
+ Settings->flag.mqtt_button_retain = XdrvMailbox.payload; // CMND_BUTTONRETAIN
}
- ResponseCmndStateText(Settings->flag.mqtt_button_retain); // CMND_BUTTONRETAIN
+ ResponseCmndStateText(Settings->flag.mqtt_button_retain); // CMND_BUTTONRETAIN
}
void CmndSwitchRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
for (uint32_t i = 1; i <= MAX_SWITCHES; i++) {
- SendKey(KEY_SWITCH, i, CLEAR_RETAIN); // Clear MQTT retain in broker
+ SendKey(KEY_SWITCH, i, CLEAR_RETAIN); // Clear MQTT retain in broker
}
}
- Settings->flag.mqtt_switch_retain = XdrvMailbox.payload; // CMND_SWITCHRETAIN
+ Settings->flag.mqtt_switch_retain = XdrvMailbox.payload; // CMND_SWITCHRETAIN
}
- ResponseCmndStateText(Settings->flag.mqtt_switch_retain); // CMND_SWITCHRETAIN
+ ResponseCmndStateText(Settings->flag.mqtt_switch_retain); // CMND_SWITCHRETAIN
}
void CmndPowerRetain(void) {
@@ -1625,49 +1625,69 @@ void CmndPowerRetain(void) {
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) { // Clear MQTT retain in broker
GetTopic_P(stemp1, STAT, TasmotaGlobal.mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings->flag.device_index_enable)); // SetOption26 - Switch between POWER or POWER1
ResponseClear();
- MqttPublish(stemp1, Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
+ MqttPublish(stemp1, true);
}
}
- Settings->flag.mqtt_power_retain = XdrvMailbox.payload; // CMND_POWERRETAIN
+ Settings->flag.mqtt_power_retain = XdrvMailbox.payload; // CMND_POWERRETAIN
if (Settings->flag.mqtt_power_retain) {
- Settings->flag4.only_json_message = 0; // SetOption90 - Disable non-json MQTT response
+ Settings->flag4.only_json_message = 0; // SetOption90 - Disable non-json MQTT response
}
}
- ResponseCmndStateText(Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
+ ResponseCmndStateText(Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN
}
void CmndSensorRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
- MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
- MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), true); // Remove retained SENSOR
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), true); // Remove retained ENERGY
}
- Settings->flag.mqtt_sensor_retain = XdrvMailbox.payload; // CMND_SENSORRETAIN
+ Settings->flag.mqtt_sensor_retain = XdrvMailbox.payload; // CMND_SENSORRETAIN
}
- ResponseCmndStateText(Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
+ ResponseCmndStateText(Settings->flag.mqtt_sensor_retain); // CMND_SENSORRETAIN
}
void CmndInfoRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
- MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO), Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
+ char stemp1[10];
+ for (uint32_t i = 1; i <= 3; i++) { // Remove retained INFO1, INFO2 and INFO3
+ snprintf_P(stemp1, sizeof(stemp1), PSTR(D_RSLT_INFO "%d"), i);
+ MqttPublishPrefixTopic_P(TELE, stemp1, true);
+ }
}
- Settings->flag5.mqtt_info_retain = XdrvMailbox.payload; // CMND_INFORETAIN
+ Settings->flag5.mqtt_info_retain = XdrvMailbox.payload; // CMND_INFORETAIN
}
- ResponseCmndStateText(Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
+ ResponseCmndStateText(Settings->flag5.mqtt_info_retain); // CMND_INFORETAIN
}
void CmndStateRetain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
if (!XdrvMailbox.payload) {
ResponseClear();
- MqttPublishPrefixTopic_P(STAT, PSTR(D_RSLT_STATE), Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
+ MqttPublishPrefixTopic_P(STAT, PSTR(D_RSLT_STATE), true); // Remove retained STATE
}
- Settings->flag5.mqtt_state_retain = XdrvMailbox.payload; // CMND_STATERETAIN
+ Settings->flag5.mqtt_state_retain = XdrvMailbox.payload; // CMND_STATERETAIN
}
- ResponseCmndStateText(Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
+ ResponseCmndStateText(Settings->flag5.mqtt_state_retain); // CMND_STATERETAIN
+}
+
+void CmndStatusRetain(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
+ if (!XdrvMailbox.payload) {
+ ResponseClear();
+ MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_STATUS), true); // Remove retained STATUS
+ char stemp1[10];
+ for (uint32_t i = 0; i <= MAX_STATUS; i++) { // Remove retained STATUS0, STATUS1 .. STATUS13
+ snprintf_P(stemp1, sizeof(stemp1), PSTR(D_CMND_STATUS "%d"), i);
+ MqttPublishPrefixTopic_P(STAT, stemp1, true);
+ }
+ }
+ Settings->flag5.mqtt_status_retain = XdrvMailbox.payload; // CMND_STATUSRETAIN
+ }
+ ResponseCmndStateText(Settings->flag5.mqtt_status_retain); // CMND_STATUSRETAIN
}
/*********************************************************************************************\
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino b/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino
index 862ebca7c..4752702b2 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_03_energy.ino
@@ -87,6 +87,8 @@ struct ENERGY {
float daily_sum; // 123.123 kWh
float total_sum; // 12345.12345 kWh total energy
float yesterday_sum; // 123.123 kWh
+ float daily_sum_import_balanced; // 123.123 kWh
+ float daily_sum_export_balanced; // 123.123 kWh
int32_t kWhtoday_delta[ENERGY_MAX_PHASES]; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to Energy.kWhtoday (HLW and CSE only)
int32_t kWhtoday_offset[ENERGY_MAX_PHASES]; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = Energy.daily
@@ -179,12 +181,12 @@ char* WebEnergyFormat(char* result, float* input, uint32_t resolution, uint32_t
}
}
#ifdef USE_ENERGY_COLUMN_GUI
- ext_snprintf_P(result, TOPSZ *2, PSTR("")); // Skip first column
+ ext_snprintf_P(result, GUISZ, PSTR("")); // Skip first column
if ((Energy.phase_count > 1) && single) { // Need to set colspan so need new columns
// | 1.23 | |
// | 1.23 | |
// | 1.23 | |
- ext_snprintf_P(result, TOPSZ *2, PSTR("%s | %*_f | | "),
+ ext_snprintf_P(result, GUISZ, PSTR("%s%*_f | | "),
result, (Energy.phase_count *2) -1, (Settings->flag5.gui_table_align)?PSTR("right"):PSTR("center"), resolution, &input[0]);
} else {
// 1.23 | |
@@ -192,16 +194,16 @@ char* WebEnergyFormat(char* result, float* input, uint32_t resolution, uint32_t
// | 1.23 | | 1.23 | | 1.23 | |
// | 1.23 | | 1.23 | | 1.23 | | 1.23 | |
for (uint32_t i = 0; i < Energy.phase_count; i++) {
- ext_snprintf_P(result, TOPSZ *2, PSTR("%s | %*_f | | "),
+ ext_snprintf_P(result, GUISZ, PSTR("%s%*_f | | "),
result, (Settings->flag5.gui_table_align)?PSTR("right"):PSTR("left"), resolution, &input[i]);
}
}
- ext_snprintf_P(result, TOPSZ *2, PSTR("%s"), result);
+ ext_snprintf_P(result, GUISZ, PSTR("%s | "), result);
#else // not USE_ENERGY_COLUMN_GUI
uint32_t index = (single) ? 1 : Energy.phase_count; // 1,2,3
result[0] = '\0';
for (uint32_t i = 0; i < index; i++) {
- ext_snprintf_P(result, TOPSZ, PSTR("%s%s%*_f"), result, (i)?" / ":"", resolution, &input[i]);
+ ext_snprintf_P(result, GUISZ, PSTR("%s%s%*_f"), result, (i)?" / ":"", resolution, &input[i]);
}
#endif // USE_ENERGY_COLUMN_GUI
return result;
@@ -238,10 +240,12 @@ void EnergyUpdateToday(void) {
Energy.total_sum = 0.0f;
Energy.yesterday_sum = 0.0f;
Energy.daily_sum = 0.0f;
+ int32_t delta_sum_balanced = 0;
for (uint32_t i = 0; i < Energy.phase_count; i++) {
if (abs(Energy.kWhtoday_delta[i]) > 1000) {
int32_t delta = Energy.kWhtoday_delta[i] / 1000;
+ delta_sum_balanced += delta;
Energy.kWhtoday_delta[i] -= (delta * 1000);
Energy.kWhtoday[i] += delta;
if (delta < 0) { // Export energy
@@ -261,6 +265,12 @@ void EnergyUpdateToday(void) {
Energy.daily_sum += Energy.daily[i];
}
+ if (delta_sum_balanced > 0) {
+ Energy.daily_sum_import_balanced += (float)delta_sum_balanced / 100000;
+ } else {
+ Energy.daily_sum_export_balanced += (float)abs(delta_sum_balanced) / 100000;
+ }
+
if (RtcTime.valid){ // We calc the difference only if we have a valid RTC time.
uint32_t energy_diff = (uint32_t)(Energy.total_sum * 100000) - RtcSettings.energy_usage.last_usage_kWhtotal;
@@ -353,6 +363,8 @@ void Energy200ms(void)
RtcSettings.energy_kWhtoday_ph[i] = 0;
Energy.start_energy[i] = 0;
// Energy.kWhtoday_delta = 0; // dont zero this, we need to carry the remainder over to tomorrow
+ Energy.daily_sum_import_balanced = 0.0;
+ Energy.daily_sum_export_balanced = 0.0;
}
EnergyUpdateToday();
#if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT)
@@ -1041,6 +1053,9 @@ void EnergyDrvInit(void) {
TasmotaGlobal.energy_driver = ENERGY_NONE;
XnrgCall(FUNC_PRE_INIT); // Find first energy driver
+ if (TasmotaGlobal.energy_driver) {
+ AddLog(LOG_LEVEL_INFO, PSTR("NRG: Init driver %d"), TasmotaGlobal.energy_driver);
+ }
}
void EnergySnsInit(void)
@@ -1151,9 +1166,9 @@ void EnergyShow(bool json) {
energy_tariff = true;
}
- char value_chr[TOPSZ * 2]; // Used by EnergyFormatIndex
- char value2_chr[TOPSZ * 2];
- char value3_chr[TOPSZ * 2];
+ char value_chr[GUISZ]; // Used by EnergyFormatIndex
+ char value2_chr[GUISZ];
+ char value3_chr[GUISZ];
if (json) {
bool show_energy_period = (0 == TasmotaGlobal.tele_period);
@@ -1185,8 +1200,10 @@ void EnergyShow(bool json) {
*/
if (!isnan(Energy.export_active[0])) {
- ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"),
- EnergyFormat(value_chr, Energy.export_active, Settings->flag2.energy_resolution));
+ ResponseAppend_P(PSTR(",\"" D_JSON_TODAY_SUM_IMPORT "\":%s,\"" D_JSON_TODAY_SUM_EXPORT "\":%s,\"" D_JSON_EXPORT_ACTIVE "\":%s"),
+ EnergyFormat(value_chr, &Energy.daily_sum_import_balanced, Settings->flag2.energy_resolution, 1),
+ EnergyFormat(value2_chr, &Energy.daily_sum_export_balanced, Settings->flag2.energy_resolution, 1),
+ EnergyFormat(value3_chr, Energy.export_active, Settings->flag2.energy_resolution));
if (energy_tariff) {
ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT D_CMND_TARIFF "\":%s"),
EnergyFormat(value_chr, energy_return, Settings->flag2.energy_resolution, 6));
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino b/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino
index 94268aa99..27acc5ae7 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino
@@ -1941,6 +1941,17 @@ void LightAnimate(void)
memcpy(Light.fade_start_10, Light.fade_cur_10, sizeof(Light.fade_start_10));
}
memcpy(Light.fade_end_10, cur_col_10, sizeof(Light.fade_start_10));
+
+ // check if PWM CT is enabled, we need a special handling of CT #16454
+ int32_t channel_ct = ChannelCT();
+ int32_t channel_white = ChannelWhite_when_PWMCT();
+ if (channel_ct >= 0 && channel_white >= 0) {
+ if (Light.fade_start_10[channel_white] == 0) {
+ // if fading from black, change the start CT to the target, otherwise we will have a wrong fade
+ Light.fade_start_10[channel_ct] = Light.fade_end_10[channel_ct];
+ }
+ }
+
Light.fade_running = true;
Light.fade_duration = 0; // set the value to zero to force a recompute
Light.fade_start = 0;
@@ -1977,17 +1988,30 @@ bool isChannelGammaCorrected(uint32_t channel) {
return true;
}
-// is the channel a regular PWM or ColorTemp control
-bool isChannelCT(uint32_t channel) {
+// Returns the channel number for PWM CT if any, or -1 if none
+int32_t ChannelCT(void) {
#ifdef ESP8266
if ((PHILIPS == TasmotaGlobal.module_type) || (Settings->flag4.pwm_ct_mode)) {
#else
if (Settings->flag4.pwm_ct_mode) {
#endif // ESP8266
- if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return true; } // PMW reserved for CT
- if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return true; } // PMW reserved for CT
+ if (LST_COLDWARM == Light.subtype) { return 1; } // PMW reserved for CT
+ if (LST_RGBCW == Light.subtype) { return 4; } // PMW reserved for CT
}
- return false;
+ return -1;
+}
+
+// Returns the white channel when PWM CT is enabled -- needed to check for brightness #16454
+int32_t ChannelWhite_when_PWMCT(void) {
+#ifdef ESP8266
+ if ((PHILIPS == TasmotaGlobal.module_type) || (Settings->flag4.pwm_ct_mode)) {
+#else
+ if (Settings->flag4.pwm_ct_mode) {
+#endif // ESP8266
+ if (LST_COLDWARM == Light.subtype) { return 0; }
+ if (LST_RGBCW == Light.subtype) { return 3; }
+ }
+ return -1;
}
// Calculate the Gamma correction, if any, for fading, using the fast Gamma curve (10 bits in+out)
@@ -2130,6 +2154,7 @@ void LightSetOutputs(const uint16_t *cur_col_10) {
// now apply the actual PWM values, adjusted and remapped 10-bits range
if (TasmotaGlobal.light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix...
+ int32_t channel_ct = ChannelCT(); // Channel for PWM CT or -1 if no CT or regular CT
#ifdef USE_PWM_DIMMER
uint16_t max_col = 0;
#ifdef USE_I2C
@@ -2150,14 +2175,16 @@ void LightSetOutputs(const uint16_t *cur_col_10) {
if (PinUsed(GPIO_PWM1, i)) {
//AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d"), i, cur_col_10[i]);
uint16_t cur_col = cur_col_10[i + Light.pwm_offset];
- if (!isChannelCT(i)) { // if CT don't use pwm_min and pwm_max
+ if (i != channel_ct) { // if CT don't use pwm_min and pwm_max
cur_col = cur_col > 0 ? changeUIntScale(cur_col, 0, Settings->pwm_range, Light.pwm_min, Light.pwm_max) : 0; // shrink to the range of pwm_min..pwm_max
}
if (!Settings->flag4.zerocross_dimmer) {
#ifdef ESP32
TasmotaGlobal.pwm_value[i] = cur_col; // mark the new expected value
+ // AddLog(LOG_LEVEL_DEBUG_MORE, "analogWrite-%i 0x%03X", i, cur_col);
#else // ESP32
analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col);
+ // AddLog(LOG_LEVEL_DEBUG_MORE, "analogWrite-%i 0x%03X", bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col);
#endif // ESP32
}
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_08_serial_bridge.ino b/tasmota/tasmota_xdrv_driver/xdrv_08_serial_bridge.ino
index a1d8279a6..e8c156a5d 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_08_serial_bridge.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_08_serial_bridge.ino
@@ -28,7 +28,7 @@
#define USE_SERIAL_BRIDGE_TEE
#ifdef ESP8266
-const uint16_t SERIAL_BRIDGE_BUFFER_SIZE = 130;
+const uint16_t SERIAL_BRIDGE_BUFFER_SIZE = MIN_INPUT_BUFFER_SIZE;
#else
const uint16_t SERIAL_BRIDGE_BUFFER_SIZE = INPUT_BUFFER_SIZE;
#endif
@@ -167,7 +167,11 @@ void SerialBridgeInput(void) {
}
ResponseJsonEnd();
- MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
+ if (Settings->flag6.mqtt_disable_sserialrec ) { // SetOption147 If it is activated, Tasmota will not publish SSerialReceived MQTT messages, but it will proccess event trigger rules
+ XdrvRulesProcess(0);
+ } else {
+ MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
+ }
serial_bridge_in_byte_counter = 0;
}
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_09_timers.ino b/tasmota/tasmota_xdrv_driver/xdrv_09_timers.ino
index bde181967..438ade4d5 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_09_timers.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_09_timers.ino
@@ -41,14 +41,14 @@
const char kTimerCommands[] PROGMEM = "|" // No prefix
D_CMND_TIMER "|" D_CMND_TIMERS
#ifdef USE_SUNRISE
- "|" D_CMND_LATITUDE "|" D_CMND_LONGITUDE
+ "|" D_CMND_LATITUDE "|" D_CMND_LONGITUDE "|" D_CMND_SUNRISE
#endif
;
void (* const TimerCommand[])(void) PROGMEM = {
&CmndTimer, &CmndTimers
#ifdef USE_SUNRISE
- , &CmndLatitude, &CmndLongitude
+ , &CmndLatitude, &CmndLongitude, &CmndSunrise
#endif
};
@@ -129,7 +129,19 @@ void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8
h (D) = -12.0 nautische Dämmerung
h (D) = -18.0 astronomische Dämmerung
*/
- const float h = SUNRISE_DAWN_ANGLE * RAD;
+ float sunrise_dawn_angle = DAWN_NORMAL;
+ switch (Settings->mbflag2.sunrise_dawn_angle) {
+ case 1:
+ sunrise_dawn_angle = DAWN_CIVIL;
+ break;
+ case 2:
+ sunrise_dawn_angle = DAWN_NAUTIC;
+ break;
+ case 3:
+ sunrise_dawn_angle = DAWN_ASTRONOMIC;
+ break;
+ }
+ const float h = sunrise_dawn_angle * RAD;
const float sin_h = sinf(h); // let GCC pre-compute the sin() at compile time
float B = Settings->latitude / (1000000.0f / RAD); // geographische Breite
@@ -508,21 +520,26 @@ void CmndTimers(void)
}
#ifdef USE_SUNRISE
-void CmndLongitude(void)
-{
+void CmndLongitude(void) {
if (XdrvMailbox.data_len) {
Settings->longitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
}
ResponseCmndFloat((float)(Settings->longitude) /1000000, 6);
}
-void CmndLatitude(void)
-{
+void CmndLatitude(void) {
if (XdrvMailbox.data_len) {
Settings->latitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
}
ResponseCmndFloat((float)(Settings->latitude) /1000000, 6);
}
+
+void CmndSunrise(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
+ Settings->mbflag2.sunrise_dawn_angle = XdrvMailbox.payload;
+ }
+ ResponseCmndNumber(Settings->mbflag2.sunrise_dawn_angle);
+}
#endif // USE_SUNRISE
/*********************************************************************************************\
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_10_rules.ino b/tasmota/tasmota_xdrv_driver/xdrv_10_rules.ino
index 3aeb88b76..6686b8770 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_10_rules.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_10_rules.ino
@@ -1459,6 +1459,18 @@ bool findNextVariableValue(char * &pVarname, float &value)
uint32_t index = sVarName.substring(5).toInt();
if (index > 0 && index <= MAX_TIMERS) {
value = Settings->timer[index -1].time;
+#if defined(USE_SUNRISE)
+ // Correct %timerN% values for sunrise/sunset timers
+ if ((1 == Settings->timer[index -1].mode) || (2 == Settings->timer[index -1].mode)) {
+ // in this context, time variable itself is merely an offset, with <720 being negative
+ value += -720 + SunMinutes(Settings->timer[index -1].mode -1);
+ if (2 == Settings->timer[index -1].mode) {
+ // To aid rule comparative statements, sunset past midnight (high lattitudes) is expressed past 24h00
+ // So sunset at 00h45 is at 24h45
+ if (value < 360) { value += 1440; }
+ }
+ }
+#endif // USE_SUNRISE
}
#if defined(USE_SUNRISE)
} else if (sVarName.equals(F("SUNRISE"))) {
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino b/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino
index 3c05b0afd..ce6d71ec9 100755
--- a/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino
@@ -80,7 +80,7 @@ uint32_t EncodeLightId(uint8_t relay_id);
uint32_t DecodeLightId(uint32_t hue_id);
char *web_send_line(char mc, char *lp);
int32_t web_send_file(char mc, char *file);
-
+char *Get_esc_char(char *cp, char *esc_chr);
#define SPECIAL_EEPMODE_SIZE 6200
#ifndef STASK_STACK
@@ -137,6 +137,10 @@ int32_t web_send_file(char mc, char *file);
#define STASK_PRIO 1
#endif
+#ifdef ESP32
+#include
+#endif
+
#ifdef USE_SCRIPT_TIMER
#include
Ticker Script_ticker1;
@@ -434,6 +438,7 @@ struct SCRIPT_MEM {
void *script_mem;
uint16_t script_mem_size;
uint8_t script_dprec;
+ char script_sepc;
uint8_t script_lzero;
uint8_t var_not_found;
uint8_t glob_error;
@@ -495,7 +500,7 @@ void flt2char(float num, char *nbuff) {
dtostrfd(num, glob_script_mem.script_dprec, nbuff);
}
// convert float to char with leading zeros
-void f2char(float num, uint32_t dprec, uint32_t lzeros, char *nbuff) {
+void f2char(float num, uint32_t dprec, uint32_t lzeros, char *nbuff, char dsep) {
dtostrfd(num, dprec, nbuff);
if (lzeros > 1) {
// check leading zeros
@@ -518,6 +523,13 @@ void f2char(float num, uint32_t dprec, uint32_t lzeros, char *nbuff) {
strcpy(nbuff,cpbuf);
}
}
+ if (dsep != '.') {
+ for (uint16_t cnt = 0; cnt < strlen(nbuff); cnt++) {
+ if (nbuff[cnt] == '.') {
+ nbuff[cnt] = dsep;
+ }
+ }
+ }
}
@@ -785,9 +797,12 @@ char *script;
// string vars
op++;
*snp_p ++= strings_p;
- while (*op!='\"') {
- if (*op==SCRIPT_EOL) break;
- *strings_p++ = *op++;
+ while (*op != '\"') {
+ if (*op == SCRIPT_EOL) break;
+ char iob;
+ op = Get_esc_char(op, &iob);
+ //*strings_p++ = *op++;
+ *strings_p++ = iob;
}
*strings_p++ = 0;
vtypes[vars].bits.is_string = 1;
@@ -966,6 +981,7 @@ char *script;
glob_script_mem.numvars = vars;
glob_script_mem.script_dprec = SCRIPT_FLOAT_PRECISION;
glob_script_mem.script_lzero = 0;
+ glob_script_mem.script_sepc = '.';
glob_script_mem.script_loglevel = LOG_LEVEL_INFO;
@@ -976,7 +992,7 @@ char *script;
} else {
char string[32];
- f2char(glob_script_mem.fvars[dvtp[count].index], glob_script_mem.script_dprec, glob_script_mem.script_lzero, string);
+ f2char(glob_script_mem.fvars[dvtp[count].index], glob_script_mem.script_dprec, glob_script_mem.script_lzero, string, '.');
toLog(string);
}
}
@@ -1042,10 +1058,14 @@ char *script;
int32_t udp_call(char *url, uint32_t port, char *sbuf) {
WiFiUDP udp;
- IPAddress adr = adr.fromString(url);
+ IPAddress adr;
+ adr.fromString(url);
+ udp.begin(port);
udp.beginPacket(adr, port);
udp.write((const uint8_t*)sbuf, strlen(sbuf));
udp.endPacket();
+ udp.flush();
+ udp.stop();
return 0;
}
@@ -1225,16 +1245,16 @@ float median_array(float *array, uint16_t len) {
uint8_t flg;
float min = FLT_MAX;
- for (uint8_t hcnt = 0; hcntnumvals & AND_FILT_MASK;
if (ipos) *ipos = mflp->index;
return mflp->rbuff;
@@ -1270,10 +1290,9 @@ char *get_array_by_name(char *lp, float **fp, uint16_t *alen, uint16_t *ipos) {
uint8_t vtype;
while (*lp == ' ') lp++;
lp = isvar(lp, &vtype, &ind, 0, 0, 0);
- if (vtype==VAR_NV) return 0;
- if (vtype&STYPE) return 0;
+ if (vtype == VAR_NV) return 0;
+ if (vtype & STYPE) return 0;
uint16_t index = glob_script_mem.type[ind.index].index;
-
if (glob_script_mem.type[ind.index].bits.is_filter) {
float *fa = Get_MFAddr(index, alen, ipos);
*fp = fa;
@@ -1287,8 +1306,8 @@ float *get_array_by_name(char *name, uint16_t *alen) {
struct T_INDEX ind;
uint8_t vtype;
isvar(name, &vtype, &ind, 0, 0, 0);
- if (vtype==VAR_NV) return 0;
- if (vtype&STYPE) return 0;
+ if (vtype == VAR_NV) return 0;
+ if (vtype & STYPE) return 0;
uint16_t index = glob_script_mem.type[ind.index].index;
if (glob_script_mem.type[ind.index].bits.is_filter) {
@@ -1763,7 +1782,16 @@ uint32_t res = 0;
}
-
+uint8_t script_hexnibble(char chr) {
+ uint8_t rVal = 0;
+ if (isdigit(chr)) {
+ rVal = chr - '0';
+ } else {
+ if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
+ if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a';
+ }
+ return rVal;
+}
#ifdef USE_LIGHT
uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value) {
@@ -1981,6 +2009,33 @@ char *isargs(char *lp, uint32_t isind) {
return lp;
}
+char *Get_esc_char(char *cp, char *esc_chr) {
+char iob = *cp;
+ if (iob == '\\') {
+ cp++;
+ if (*cp == 't') {
+ iob = '\t';
+ } else if (*cp == 'n') {
+ iob = '\n';
+ } else if (*cp == 'r') {
+ iob = '\r';
+ } else if (*cp == '0' && *(cp + 1) == 'x') {
+ cp += 2;
+ iob = strtol(cp, 0, 16);
+ cp++;
+ } else if (*cp == '\\') {
+ iob = '\\';
+ }
+ else {
+ cp--;
+ }
+ }
+ *esc_chr = iob;
+ cp++;
+ return cp;
+}
+
+
char *isget(char *lp, char *sp, uint32_t isind, struct GVARS *gv) {
float fvar;
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
@@ -2045,29 +2100,9 @@ char *isvar(char *lp, uint8_t *vtype, struct T_INDEX *tind, float *fp, char *sp,
lp++;
while (*lp != '"') {
if (*lp == 0 || *lp == SCRIPT_EOL) break;
- uint8_t iob = *lp;
- if (iob == '\\') {
- lp++;
- if (*lp == 't') {
- iob = '\t';
- } else if (*lp == 'n') {
- iob = '\n';
- } else if (*lp == 'r') {
- iob = '\r';
- } else if (*lp == '0' && *(lp+1) == 'x') {
- lp += 2;
- iob = strtol(lp, 0, 16);
- lp++;
- } else if (*lp == '\\') {
- iob = '\\';
- } else {
- lp--;
- }
- if (sp) *sp++ = iob;
- } else {
- if (sp) *sp++ = iob;
- }
- lp++;
+ char iob;
+ lp = Get_esc_char(lp, &iob);
+ if (sp) *sp++ = iob;
}
if (sp) *sp = 0;
*vtype = STR_RES;
@@ -2527,6 +2562,18 @@ chknext:
tind->index = SCRIPT_CBSIZE;
goto exit_settable;
}
+#ifdef USE_W8960
+extern void W8960_SetGain(uint8_t sel, uint16_t value);
+
+ if (!strncmp(lp, "codec(", 6)) {
+ float sel;
+ lp = GetNumericArgument(lp + 6, OPER_EQU, &sel, gv);
+ lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv);
+ W8960_SetGain(sel, fvar);
+ fvar = 0;
+ goto nfuncexit;
+ }
+#endif
break;
case 'd':
if (!strncmp(vname, "day", 3)) {
@@ -2541,6 +2588,10 @@ chknext:
lp = GetNumericArgument(lp + 3, OPER_EQU, &fvar, gv);
while (*lp==' ') lp++;
glob_script_mem.script_lzero = fvar;
+ if (*lp == ',' || *lp == '.') {
+ glob_script_mem.script_sepc = *lp;
+ lp++;
+ }
lp = GetNumericArgument(lp , OPER_EQU, &fvar, gv);
while (*lp==' ') lp++;
glob_script_mem.script_dprec = fvar;
@@ -3380,7 +3431,23 @@ chknext:
goto nfuncexit;
}
#endif
+ if (!strncmp(lp, "hstr(", 5)) {
+ char hstr[SCRIPT_MAXSSIZE];
+ lp = GetStringArgument(lp + 5, OPER_EQU, hstr, 0);
+ uint16_t cnt;
+ uint16_t slen = strlen(hstr);
+ slen &= 0xfffe;
+ for (cnt = 0; cnt < slen; cnt += 2) {
+ hstr[cnt / 2] = (script_hexnibble(hstr[cnt]) << 4) | script_hexnibble(hstr[cnt + 1] );
+ }
+ hstr[cnt / 2 + 1] = 0;
+ if (sp) strlcpy(sp, hstr, strlen(hstr));
+ len = 0;
+ lp++;
+ goto strexit;
+ }
break;
+
case 'i':
if (!strncmp(lp, "ins(", 4)) {
char s1[SCRIPT_MAXSSIZE];
@@ -3478,6 +3545,34 @@ chknext:
goto nfuncexit;
}
#endif // USE_SCRIPT_I2C
+
+#ifdef ESP32
+#ifdef USE_I2S_AUDIO
+ if (!strncmp(lp, "i2sw(", 5)) {
+ float port;
+ lp = GetNumericArgument(lp + 5, OPER_EQU, &port, gv);
+ uint16_t alen = 0;
+ float *fa = 0;
+ lp = get_array_by_name(lp, &fa, &alen, 0);
+ if (!fa) {
+ fvar = -1;
+ goto nfuncexit;
+ }
+ lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv);
+ uint32_t bytes_written;
+ int16_t *wp = (int16_t*)special_malloc(alen * 2);
+ if (wp) {
+ for (uint16_t cnt = 0; cnt < alen; cnt++) {
+ wp[cnt] = fa[cnt];
+ }
+ i2s_write((i2s_port_t)port, (const uint8_t*)wp, fvar, &bytes_written, 0);
+ free(wp);
+ fvar = bytes_written;
+ }
+ goto nfuncexit;
+ }
+#endif // USE_I2S_AUDIO
+#endif // ESP32
break;
#ifdef USE_KNX
@@ -3834,7 +3929,6 @@ chknext:
#endif
*/
-#ifdef ESP32
if (!strncmp(lp, "rr(", 3)) {
lp+=4;
len = 0;
@@ -3848,7 +3942,6 @@ chknext:
}
goto strexit;
}
-#endif
break;
case 's':
@@ -3909,7 +4002,13 @@ chknext:
lp = GetStringArgument(lp + 3, OPER_EQU, str, 0);
SCRIPT_SKIP_SPACES
char token[2];
- token[0] = *lp++;
+ if (*lp == '\'') {
+ lp++;
+ lp = Get_esc_char(lp, token);
+ lp++;
+ } else {
+ token[0] = *lp++;
+ }
token[1] = 0;
SCRIPT_SKIP_SPACES
lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv);
@@ -3963,8 +4062,10 @@ chknext:
lp += 2;
uint8_t dprec = glob_script_mem.script_dprec;
uint8_t lzero = glob_script_mem.script_lzero;
+ char dsep = glob_script_mem.script_sepc;
if (isdigit(*lp)) {
- if (*(lp + 1) == '.') {
+ if (*(lp + 1) == '.' || *(lp + 1) == ',') {
+ dsep = *(lp + 1);
lzero = *lp & 0xf;
lp += 2;
dprec = *lp & 0xf;
@@ -3976,7 +4077,7 @@ chknext:
}
lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv);
char str[SCRIPT_MAXSSIZE];
- f2char(fvar, dprec, lzero, str);
+ f2char(fvar, dprec, lzero, str, dsep);
if (sp) strlcpy(sp, str, glob_script_mem.max_ssize);
lp++;
len = 0;
@@ -4625,6 +4726,7 @@ extern char *SML_GetSVal(uint32_t index);
script_sspi_trans(index, fpd, len, fvar);
break;
}
+ lp++;
len = 0;
goto exit;
}
@@ -4782,6 +4884,16 @@ extern char *SML_GetSVal(uint32_t index);
goto nfuncexit;
}
#endif
+ if (!strncmp(lp, "tc(", 3)) {
+ lp = GetNumericArgument(lp + 3, OPER_EQU, &fvar, gv);
+ lp++;
+ if (sp) {
+ sp[0] = fvar;
+ sp[1] = 0;
+ }
+ len = 0;
+ goto strexit;
+ }
break;
case 'u':
if (!strncmp(vname, "uptime", 6)) {
@@ -5375,6 +5487,7 @@ void Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dst
uint16_t count;
uint8_t vtype;
uint8_t dprec = glob_script_mem.script_dprec;
+ char dsep = glob_script_mem.script_sepc;
uint8_t lzero = glob_script_mem.script_lzero;
float fvar;
cp = srcbuf;
@@ -5389,7 +5502,8 @@ void Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dst
dstbuf[count] = *cp++;
} else {
if (isdigit(*cp)) {
- if (*(cp+1) == '.') {
+ if (*(cp + 1) == '.' || *(cp + 1) == ',') {
+ dsep = *(cp + 1);
lzero = *cp & 0xf;
cp+=2;
}
@@ -5398,6 +5512,7 @@ void Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dst
} else {
dprec = glob_script_mem.script_dprec;
lzero = glob_script_mem.script_lzero;
+ dsep = glob_script_mem.script_sepc;
}
if (*cp=='(') {
// math expression
@@ -5409,7 +5524,7 @@ void Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dst
glob_script_mem.glob_error = 0;
cp = GetStringArgument(slp, OPER_EQU, string, 0);
} else {
- f2char(fvar, dprec, lzero, string);
+ f2char(fvar, dprec, lzero, string, dsep);
}
uint8_t slen = strlen(string);
if (count + slen < dstsize - 1) {
@@ -5423,7 +5538,7 @@ void Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dst
// found variable as result
if (vtype==NUM_RES || (vtype&STYPE)==0) {
// numeric result
- f2char(fvar, dprec, lzero, string);
+ f2char(fvar, dprec, lzero, string, dsep);
} else {
// string result
}
@@ -5800,9 +5915,15 @@ int16_t retval;
return retval;
}
+#define SCRIPT_LOOP_NEST 3
int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
- uint8_t vtype = 0, sindex, xflg, floop = 0, globvindex, fromscriptcmd = 0;
- char *lp_next;
+ uint8_t vtype = 0, sindex, xflg, globvindex, fromscriptcmd = 0;
+ // 22 bytes per nested loop
+ uint8_t floop[SCRIPT_LOOP_NEST] = {0, 0, 0};
+ int8_t loopdepth = -1;
+ char *lp_next[SCRIPT_LOOP_NEST];
+ char *cv_ptr[SCRIPT_LOOP_NEST];
+ float *cv_count[SCRIPT_LOOP_NEST], cv_max[SCRIPT_LOOP_NEST], cv_inc[SCRIPT_LOOP_NEST];
int16_t globaindex, saindex;
struct T_INDEX ind;
uint8_t operand, lastop, numeric = 1, if_state[IF_NEST], if_exe[IF_NEST], if_result[IF_NEST], and_or, ifstck = 0;
@@ -5810,8 +5931,8 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
if_result[ifstck] = 0;
if_exe[ifstck] = 1;
char cmpstr[SCRIPT_MAXSSIZE];
- float *dfvar, *cv_count, cv_max, cv_inc;
- char *cv_ptr;
+ float *dfvar;
+
float fvar = 0, fvar1, sysvar, swvar;
uint8_t section = 0, sysv_type = 0, swflg = 0;
@@ -5824,7 +5945,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
}
uint8_t check = 0;
- if (tlen<0) {
+ if (tlen < 0) {
tlen = abs(tlen);
check = 1;
}
@@ -5832,22 +5953,23 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
while (1) {
// check line
- // skip leading spaces
- startline:
- SCRIPT_SKIP_SPACES
- while (*lp == '\t') {lp++;}
+ // skip leading spaces and tabs
+startline:
+ while (*lp == '\t' || *lp == ' ') {
+ lp++;
+ }
// skip empty line
SCRIPT_SKIP_EOL
// skip comment
- if (*lp==';') goto next_line;
+ if (*lp == ';') goto next_line;
if (!*lp) break;
if (section) {
// we are in section
- if (*lp=='>') {
+ if (*lp == '>') {
return 0;
}
- if (*lp=='#') {
+ if (*lp == '#') {
return 0;
}
glob_script_mem.var_not_found = 0;
@@ -5864,59 +5986,59 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
if (!strncmp(lp, "if", 2)) {
lp += 2;
- if (ifstck=2) {
+ } else if (!strncmp(lp, "endif", 5) && if_state[ifstck] >= 2) {
lp += 5;
if (ifstck>0) {
if_state[ifstck] = 0;
ifstck--;
}
goto next_line;
- } else if (!strncmp(lp, "or", 2) && if_state[ifstck]==1) {
+ } else if (!strncmp(lp, "or", 2) && if_state[ifstck] == 1) {
lp += 2;
and_or = 1;
- } else if (!strncmp(lp, "and", 3) && if_state[ifstck]==1) {
+ } else if (!strncmp(lp, "and", 3) && if_state[ifstck] == 1) {
lp += 3;
and_or = 2;
}
- if (*lp=='{' && if_state[ifstck]==1) {
+ if (*lp == '{' && if_state[ifstck] == 1) {
lp += 1; // then
if_state[ifstck] = 2;
- if (if_exe[ifstck - 1]) if_exe[ifstck]=if_result[ifstck];
- } else if (*lp=='{' && if_state[ifstck]==3) {
+ if (if_exe[ifstck - 1]) if_exe[ifstck] = if_result[ifstck];
+ } else if (*lp == '{' && if_state[ifstck] == 3) {
lp += 1; // after else
//if_state[ifstck]=3;
- } else if (*lp=='}' && if_state[ifstck]>=2) {
+ } else if (*lp == '}' && if_state[ifstck] >= 2) {
lp++; // must check for else
char *slp = lp;
uint8_t iselse = 0;
- for (uint8_t count = 0; count<8;count++) {
- if (*lp=='}') {
+ for (uint8_t count = 0; count < 8; count++) {
+ if (*lp == '}') {
// must be endif
break;
}
if (!strncmp(lp, "else", 4)) {
// is before else, no endif
if_state[ifstck] = 3;
- if (if_exe[ifstck-1]) if_exe[ifstck]=!if_result[ifstck];
+ if (if_exe[ifstck - 1]) if_exe[ifstck] =! if_result[ifstck];
lp += 4;
iselse = 1;
SCRIPT_SKIP_SPACES
- if (*lp=='{') lp++;
+ if (*lp == '{') lp++;
break;
}
lp++;
@@ -5924,7 +6046,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
if (!iselse) {
lp = slp;
// endif
- if (ifstck>0) {
+ if (ifstck > 0) {
if_state[ifstck] = 0;
ifstck--;
}
@@ -5937,28 +6059,29 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
// simple implementation, zero loop count not supported
lp += 3;
SCRIPT_SKIP_SPACES
- lp_next = 0;
+ loopdepth++;
+ if (loopdepth >= SCRIPT_LOOP_NEST) {
+ loopdepth = SCRIPT_LOOP_NEST - 1;
+ }
+ lp_next[loopdepth] = 0;
lp = isvar(lp, &vtype, &ind, 0, 0, gv);
- if ((vtype!=VAR_NV) && (vtype&STYPE)==0) {
+ if ((vtype != VAR_NV) && (vtype & STYPE) == 0) {
// numeric var
uint8_t index = glob_script_mem.type[ind.index].index;
- cv_count = &glob_script_mem.fvars[index];
- SCRIPT_SKIP_SPACES
- lp = GetNumericArgument(lp, OPER_EQU, cv_count, 0);
- SCRIPT_SKIP_SPACES
- lp = GetNumericArgument(lp, OPER_EQU, &cv_max, 0);
- SCRIPT_SKIP_SPACES
- lp = GetNumericArgument(lp, OPER_EQU, &cv_inc, 0);
+ cv_count[loopdepth] = &glob_script_mem.fvars[index];
+ lp = GetNumericArgument(lp, OPER_EQU, cv_count[loopdepth], 0);
+ lp = GetNumericArgument(lp, OPER_EQU, &cv_max[loopdepth], 0);
+ lp = GetNumericArgument(lp, OPER_EQU, &cv_inc[loopdepth], 0);
//SCRIPT_SKIP_EOL
- cv_ptr = lp;
- if (*cv_count<=cv_max && cv_inc>0) {
+ cv_ptr[loopdepth] = lp;
+ if (*cv_count[loopdepth] <= cv_max[loopdepth] && cv_inc[loopdepth] > 0) {
// inc loop
- floop = 1;
+ floop[loopdepth] = 1;
} else {
// dec loop
- floop = 2;
- if (cv_inc>0) {
- floop = 1;
+ floop[loopdepth] = 2;
+ if (cv_inc[loopdepth] > 0) {
+ floop[loopdepth] = 1;
}
}
} else {
@@ -5966,23 +6089,40 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
toLogEOL("for error", lp);
}
} else if (!strncmp(lp, "next", 4)) {
- lp_next = lp;
- if (floop>0) {
- // for next loop
- *cv_count += cv_inc;
- if (floop==1) {
- if (*cv_count<=cv_max) {
- lp = cv_ptr;
+getnext:
+ if (loopdepth >= 0) {
+ lp_next[loopdepth] = lp;
+ if (floop[loopdepth] > 0) {
+ // for next loop
+ *cv_count[loopdepth] += cv_inc[loopdepth];
+ if (floop[loopdepth] == 1) {
+ if (*cv_count[loopdepth] <= cv_max[loopdepth]) {
+ lp = cv_ptr[loopdepth];
+ } else {
+ lp += 4;
+ floop[loopdepth] = 0;
+ loopdepth--;
+ if (loopdepth < -1) {
+ loopdepth = -1;
+ }
+ }
} else {
- lp += 4;
- floop = 0;
+ if (*cv_count[loopdepth] >= cv_max[loopdepth]) {
+ lp = cv_ptr[loopdepth];
+ } else {
+ lp += 4;
+ floop[loopdepth] = 0;
+ loopdepth--;
+ if (loopdepth < -1) {
+ loopdepth = -1;
+ }
+ }
}
} else {
- if (*cv_count>=cv_max) {
- lp = cv_ptr;
- } else {
- lp += 4;
- floop = 0;
+ lp += 4;
+ loopdepth--;
+ if (loopdepth < -1) {
+ loopdepth = -1;
}
}
}
@@ -5993,7 +6133,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
SCRIPT_SKIP_SPACES
char *slp = lp;
lp = GetNumericArgument(lp, OPER_EQU, &swvar, 0);
- if (glob_script_mem.glob_error==1) {
+ if (glob_script_mem.glob_error == 1) {
// was string, not number
lp = slp;
// get the string
@@ -6008,7 +6148,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
float cvar;
if (!(swflg & 0x80)) {
lp = GetNumericArgument(lp, OPER_EQU, &cvar, 0);
- if (swvar!=cvar) {
+ if (swvar != cvar) {
swflg = 2;
} else {
swflg = 1;
@@ -6022,20 +6162,20 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
swflg = 0x82;
}
}
- } else if (!strncmp(lp, "ends", 4) && swflg>0) {
+ } else if (!strncmp(lp, "ends", 4) && swflg > 0) {
lp += 4;
swflg = 0;
}
- if ((swflg & 3)==2) goto next_line;
+ if ((swflg & 3) == 2) goto next_line;
SCRIPT_SKIP_SPACES
//SCRIPT_SKIP_EOL
- if (*lp==SCRIPT_EOL) {
+ if (*lp == SCRIPT_EOL) {
goto next_line;
}
//toLogN(lp,16);
- if (!if_exe[ifstck] && if_state[ifstck]!=1) goto next_line;
+ if (!if_exe[ifstck] && if_state[ifstck] != 1) goto next_line;
#ifdef IFTHEN_DEBUG
sprintf(tbuff, "stack=%d,exe=%d,state=%d,cmpres=%d execute line: ", ifstck, if_exe[ifstck], if_state[ifstck], if_result[ifstck]);
@@ -6066,23 +6206,28 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
goto next_line;
} else if (!strncmp(lp, "break", 5)) {
lp += 5;
- if (floop) {
- // should break loop
- if (lp_next) {
- lp = lp_next;
+ if (loopdepth >= 0) {
+ if (floop[loopdepth] ) {
+ // should break loop
+ if (lp_next[loopdepth]) {
+ lp = lp_next[loopdepth];
+ }
+ floop[loopdepth] = 0;
+ goto getnext;
}
- floop = 0;
} else {
section = 99;
// leave immediately
+ goto next_line;
}
- goto next_line;
+
} else if (!strncmp(lp, "dp", 2) && isdigit(*(lp + 2))) {
lp += 2;
// number precision
- if (*(lp + 1)== '.') {
+ if (*(lp + 1) == '.' || *(lp + 1) == ',' ) {
+ glob_script_mem.script_sepc = *(lp + 1);
glob_script_mem.script_lzero = atoi(lp);
- lp+=2;
+ lp += 2;
}
glob_script_mem.script_dprec = atoi(lp);
goto next_line;
@@ -6119,7 +6264,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
}
SCRIPT_SKIP_SPACES
uint8_t mode = 0;
- if ((*lp=='I') || (*lp=='O') || (*lp=='P')) {
+ if ((*lp == 'I') || (*lp == 'O') || (*lp == 'P')) {
switch (*lp) {
case 'I':
mode = 0;
@@ -6136,10 +6281,10 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
mode = fvar;
}
- uint8_t pm=0;
- if (mode==0) pm = INPUT;
- if (mode==1) pm = OUTPUT;
- if (mode==2) pm = INPUT_PULLUP;
+ uint8_t pm = 0;
+ if (mode == 0) pm = INPUT;
+ if (mode == 1) pm = OUTPUT;
+ if (mode == 2) pm = INPUT_PULLUP;
pinMode(pinnr, pm);
goto next_line;
} else if (!strncmp(lp, "spin(", 5)) {
@@ -6169,16 +6314,16 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
#ifdef USE_WS2812
else if (!strncmp(lp, "ws2812(", 7)) {
lp = isvar(lp + 7, &vtype, &ind, 0, 0, gv);
- if (vtype!=VAR_NV) {
+ if (vtype != VAR_NV) {
SCRIPT_SKIP_SPACES
- if (*lp!=')') {
+ if (*lp != ')') {
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
} else {
fvar = 0;
}
// found variable as result
uint8_t index = glob_script_mem.type[ind.index].index;
- if ((vtype&STYPE)==0) {
+ if ((vtype & STYPE) == 0) {
// numeric result
if (glob_script_mem.type[ind.index].bits.is_filter) {
uint16_t len = 0;
@@ -6207,7 +6352,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
else if (!strncmp(lp, "pwm", 3)) {
lp += 3;
uint8_t channel = 1;
- if (*(lp+1)=='(') {
+ if (*(lp+1) == '(') {
channel = *lp & 0x0f;
#ifdef ESP8266
if (channel > 5) {channel = 5;}
@@ -6218,7 +6363,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
if (channel < 1) {channel = 1;}
lp += 2;
} else {
- if (*lp=='(') {
+ if (*lp == '(') {
lp++;
} else {
goto next_line;
@@ -6227,7 +6372,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
SCRIPT_SKIP_SPACES
float fvar1=4000;
- if (*lp!=')') {
+ if (*lp != ')') {
lp = GetNumericArgument(lp, OPER_EQU, &fvar1, 0);
}
esp_pwm(fvar, fvar1, channel);
@@ -6269,7 +6414,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
#if defined(USE_SENDMAIL) || defined(USE_ESP32MAIL)
else if (!strncmp(lp, "mail", 4)) {
- lp+=5;
+ lp += 5;
//char tmp[256];
char *tmp = (char*)malloc(256);
if (tmp) {
@@ -6280,16 +6425,16 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
goto next_line;
}
#endif
- else if (!strncmp(lp,"=>",2) || !strncmp(lp,"->",2) || !strncmp(lp,"+>",2) || !strncmp(lp,"print",5)) {
+ else if (!strncmp(lp, "=>", 2) || !strncmp(lp, "->", 2) || !strncmp(lp, "+>", 2) || !strncmp(lp, "print", 5)) {
// execute cmd
uint8_t sflag = 0,pflg = 0,svmqtt,swll;
- if (*lp=='p') {
+ if (*lp == 'p') {
pflg = 1;
lp += 5;
}
else {
- if (*lp=='-') sflag = 1;
- if (*lp=='+') sflag = 2;
+ if (*lp == '-') sflag = 1;
+ if (*lp == '+') sflag = 2;
lp += 2;
}
char *slp = lp;
@@ -6300,7 +6445,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
uint16_t count;
for (count = 0; count < glob_script_mem.cmdbuffer_size-2; count++) {
//if (*lp=='\r' || *lp=='\n' || *lp=='}') {
- if (!*lp || *lp=='\r' || *lp=='\n') {
+ if (!*lp || *lp == '\r' || *lp == '\n') {
cmd[count] = 0;
break;
}
@@ -6384,7 +6529,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
if (gv) globaindex = gv->numind;
else globaindex = -1;
uint8_t index = glob_script_mem.type[ind.index].index;
- if ((vtype&STYPE)==0) {
+ if ((vtype & STYPE) == 0) {
// numeric result
if (ind.bits.settable || ind.bits.is_filter) {
dfvar = &sysvar;
@@ -6401,7 +6546,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
SCRIPT_SKIP_SPACES
lp = getop(lp, &lastop);
#ifdef SCRIPT_LM_SUB
- if (*lp=='#') {
+ if (*lp == '#') {
// subroutine
lp = eval_sub(lp, &fvar, 0);
} else {
@@ -6500,8 +6645,8 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
glob_script_mem.script_loglevel = *dfvar;
break;
case SCRIPT_TELEPERIOD:
- if (*dfvar<10) *dfvar = 10;
- if (*dfvar>300) *dfvar = 300;
+ if (*dfvar < 10) *dfvar = 10;
+ if (*dfvar > 300) *dfvar = 300;
Settings->tele_period = *dfvar;
break;
case SCRIPT_EVENT_HANDLED:
@@ -6534,7 +6679,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
SCRIPT_SKIP_SPACES
lp = getop(lp, &lastop);
#ifdef SCRIPT_LM_SUB
- if (*lp=='#') {
+ if (*lp == '#') {
// subroutine
lp = eval_sub(lp, 0, str);
} else {
@@ -6559,17 +6704,17 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
script_udp_sendvar(varname, 0, str);
}
#endif //USE_SCRIPT_GLOBVARS
- if (saindex>=0) {
- if (lastop==OPER_EQU) {
+ if (saindex >= 0) {
+ if (lastop == OPER_EQU) {
strlcpy(glob_script_mem.last_index_string[glob_script_mem.sind_num] + (saindex * glob_script_mem.max_ssize), str, glob_script_mem.max_ssize);
- } else if (lastop==OPER_PLSEQU) {
+ } else if (lastop == OPER_PLSEQU) {
strncat(glob_script_mem.last_index_string[glob_script_mem.sind_num] + (saindex * glob_script_mem.max_ssize), str, glob_script_mem.max_ssize);
}
gv->strind = -1;
} else {
- if (lastop==OPER_EQU) {
+ if (lastop == OPER_EQU) {
strlcpy(glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize), str, glob_script_mem.max_ssize);
- } else if (lastop==OPER_PLSEQU) {
+ } else if (lastop == OPER_PLSEQU) {
strncat(glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize), str, glob_script_mem.max_ssize);
}
}
@@ -6578,7 +6723,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
}
SCRIPT_SKIP_SPACES
- if (*lp=='{' && if_state[ifstck]==3) {
+ if (*lp == '{' && if_state[ifstck] == 3) {
lp += 1; // else
//if_state[ifstck]=3;
}
@@ -6587,7 +6732,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
} else {
//Serial.printf(">> decode %s\n",lp );
// decode line
- if (*lp=='>' && tlen==1) {
+ if (*lp == '>' && tlen == 1) {
// called from cmdline
lp++;
section = 1;
@@ -6615,7 +6760,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
numeric = 1;
glob_script_mem.glob_error = 0;
argptr = GetNumericArgument((char*)ctype + 1, OPER_EQU, &fparam, 0);
- if (glob_script_mem.glob_error==1) {
+ if (glob_script_mem.glob_error == 1) {
// was string, not number
numeric = 0;
// get the string
@@ -6664,7 +6809,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, struct GVARS *gv) {
// next line
next_line:
if (section == 99) return 0;
- if (*lp==SCRIPT_EOL) {
+ if (*lp == SCRIPT_EOL) {
lp++;
} else {
lp = strchr(lp, SCRIPT_EOL);
@@ -8284,6 +8429,11 @@ String ScriptUnsubscribe(const char * data, int data_len)
#if defined(ESP32) && defined(USE_UFILESYS) && defined(USE_SCRIPT_ALT_DOWNLOAD)
+
+#ifndef SCRIPT_DLPORT
+#define SCRIPT_DLPORT 82
+#endif
+
ESP8266WebServer *http82_Server;
bool download82_busy;
@@ -8343,15 +8493,15 @@ void WebServer82Init(void) {
if (http82_Server != nullptr) {
return;
}
- http82_Server = new ESP8266WebServer(82);
+ http82_Server = new ESP8266WebServer(SCRIPT_DLPORT);
if (http82_Server != nullptr) {
http82_Server->on(UriGlob("/ufs/*"), HTTP_GET, ScriptServeFile82);
http82_Server->on("/", HTTP_GET, Handle82Root);
http82_Server->onNotFound(Handle82NotFound);
http82_Server->begin();
- AddLog(LOG_LEVEL_INFO, PSTR("HTTP Server 82 started"));
+ AddLog(LOG_LEVEL_INFO, PSTR("HTTP DL Server started on port: %d "), SCRIPT_DLPORT);
} else {
- AddLog(LOG_LEVEL_INFO, PSTR("HTTP Server 82 failed"));
+ AddLog(LOG_LEVEL_INFO, PSTR("HTTP DL Server failed"));
}
}
@@ -8954,16 +9104,16 @@ uint16_t cipos = 0;
}
char *gc_send_labels(char *lp,uint32_t anum) {
- WSContentSend_PD("[");
+ WSContentSend_P("[");
for (uint32_t cnt = 0; cnt < anum + 1; cnt++) {
char label[SCRIPT_MAXSSIZE];
lp = GetStringArgument(lp, OPER_EQU, label, 0);
SCRIPT_SKIP_SPACES
- WSContentSend_PD(SCRIPT_MSG_GTE1, label);
+ WSContentSend_P(SCRIPT_MSG_GTE1, label);
//Serial.printf("labels %s\n",label);
- if (cnt1) proz -= 2;
- if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_START_TBL);
- else WSContentSend_PD(SCRIPT_MSG_BUT_START);
+ if (optflg) WSContentSend_P(SCRIPT_MSG_BUT_START_TBL);
+ else WSContentSend_P(SCRIPT_MSG_BUT_START);
for (uint32_t cnt = 0; cnt < bcnt; cnt++) {
float val;
char *slp = lp;
@@ -9378,17 +9528,17 @@ const char *gc_str;
if (!optflg) proz += 2;
}
if (!optflg) {
- WSContentSend_PD(SCRIPT_MSG_BUTTONa, proz, uval, vname, cp);
+ WSContentSend_P(SCRIPT_MSG_BUTTONa, proz, uval, vname, cp);
} else {
- WSContentSend_PD(SCRIPT_MSG_BUTTONa_TBL, proz, uval, vname, cp);
+ WSContentSend_P(SCRIPT_MSG_BUTTONa_TBL, proz, uval, vname, cp);
}
if (bcnt > 1 && cnt < bcnt - 1) {
- if (!optflg) WSContentSend_PD(SCRIPT_MSG_BUTTONb, 2);
+ if (!optflg) WSContentSend_P(SCRIPT_MSG_BUTTONb, 2);
}
lp += 4;
}
- if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_STOP_TBL);
- else WSContentSend_PD(SCRIPT_MSG_BUT_STOP);
+ if (optflg) WSContentSend_P(SCRIPT_MSG_BUT_STOP_TBL);
+ else WSContentSend_P(SCRIPT_MSG_BUT_STOP);
} else if (!strncmp(lin, "tm(", 3)) {
// time only HH:MM
@@ -9418,7 +9568,7 @@ const char *gc_str;
const char *max = PSTR("23:59");
const char *styp = PSTR("sivat");
WCS_DIV(specopt);
- WSContentSend_PD(SCRIPT_MSG_TEXTINP_U, center, label, type, vstr, min, max, tsiz, styp, vname);
+ WSContentSend_P(SCRIPT_MSG_TEXTINP_U, center, label, type, vstr, min, max, tsiz, styp, vname);
WCS_DIV(specopt | WSO_STOP_DIV);
} else if (!strncmp(lin, "tx(", 3)) {
// text
@@ -9459,16 +9609,16 @@ const char *gc_str;
SCRIPT_SKIP_SPACES
WCS_DIV(specopt);
const char *styp = PSTR("siva");
- WSContentSend_PD(SCRIPT_MSG_TEXTINP_U, center, label, type, str, min, max, tsiz, styp, vname);
+ WSContentSend_P(SCRIPT_MSG_TEXTINP_U, center, label, type, str, min, max, tsiz, styp, vname);
WCS_DIV(specopt | WSO_STOP_DIV);
} else {
WCS_DIV(specopt);
- WSContentSend_PD(SCRIPT_MSG_TEXTINP, center, label, str, tsiz, vname);
+ WSContentSend_P(SCRIPT_MSG_TEXTINP, center, label, str, tsiz, vname);
WCS_DIV(specopt | WSO_STOP_DIV);
}
} else {
WCS_DIV(specopt);
- WSContentSend_PD(SCRIPT_MSG_TEXTINP, center, label, str, tsiz, vname);
+ WSContentSend_P(SCRIPT_MSG_TEXTINP, center, label, str, tsiz, vname);
WCS_DIV(specopt | WSO_STOP_DIV);
}
lp++;
@@ -9514,18 +9664,18 @@ const char *gc_str;
dtostrfd(max, dprec, maxstr);
dtostrfd(step, dprec, stepstr);
WCS_DIV(specopt);
- WSContentSend_PD(SCRIPT_MSG_NUMINP, center, label, minstr, maxstr, stepstr, vstr, tsiz, vname);
+ WSContentSend_P(SCRIPT_MSG_NUMINP, center, label, minstr, maxstr, stepstr, vstr, tsiz, vname);
WCS_DIV(specopt | WSO_STOP_DIV);
lp++;
} else {
if (mc == 'w' || (specopt & WSO_FORCEPLAIN)) {
- WSContentSend_PD(PSTR("%s"), lin);
+ WSContentSend_P(PSTR("%s"), lin);
} else {
if (optflg) {
- WSContentSend_PD(PSTR(" %s "), lin);
+ WSContentSend_P(PSTR("%s "), lin);
} else {
- WSContentSend_PD(PSTR("{s}%s{e}"), lin);
+ WSContentSend_P(PSTR("{s}%s{e}"), lin);
}
}
}
@@ -9551,7 +9701,7 @@ exgc:
uint16_t len = (uint32_t)cp - (uint32_t)lin;
strncpy(valstr, lin, len);
valstr[len] = 0;
- WSContentSend_PD(PSTR("%s"), valstr);
+ WSContentSend_P(PSTR("%s"), valstr);
float *fpd = 0;
uint16_t alend;
uint16_t ipos;
@@ -9576,14 +9726,14 @@ exgc:
ipos = 0;
}
if (cnt == 0) {
- WSContentSend_PD(PSTR("%s"), valstr);
+ WSContentSend_P(PSTR("%s"), valstr);
} else {
- WSContentSend_PD(PSTR(",%s"), valstr);
+ WSContentSend_P(PSTR(",%s"), valstr);
}
}
}
lp++;
- WSContentSend_PD(PSTR("%s"), lp);
+ WSContentSend_P(PSTR("%s"), lp);
return lp;
}
@@ -9594,11 +9744,11 @@ exgc:
uint16_t len = (uint32_t)cp - (uint32_t)lin;
strncpy(valstr, lin, len);
valstr[len] = 0;
- WSContentSend_PD(PSTR("%s"), valstr);
+ WSContentSend_P(PSTR("%s"), valstr);
scripter_sub(cp , 0);
cp = strchr(cp, ')');
if (cp) {
- WSContentSend_PD(PSTR("%s"), cp + 1);
+ WSContentSend_P(PSTR("%s"), cp + 1);
}
return lp;
}
@@ -9620,7 +9770,7 @@ exgc:
lp++;
if (!(google_libs & GLIBS_MAIN)) {
google_libs |= GLIBS_MAIN;
- WSContentSend_PD(SCRIPT_MSG_GTABLE);
+ WSContentSend_P(SCRIPT_MSG_GTABLE);
}
gc_str = GC_type(gs_ctype);
@@ -9629,24 +9779,24 @@ exgc:
case 'g':
if (!(google_libs & GLIBS_GAUGE)) {
google_libs |= GLIBS_GAUGE;
- WSContentSend_PD(SCRIPT_MSG_GAUGE);
+ WSContentSend_P(SCRIPT_MSG_GAUGE);
}
break;
case 't':
if (!(google_libs & GLIBS_TABLE)) {
google_libs |= GLIBS_TABLE;
- WSContentSend_PD(SCRIPT_MSG_TABLE);
+ WSContentSend_P(SCRIPT_MSG_TABLE);
}
break;
case 'T':
if (!(google_libs & GLIBS_TIMELINE)) {
google_libs |= GLIBS_TIMELINE;
- WSContentSend_PD(SCRIPT_MSG_TIMELINE);
+ WSContentSend_P(SCRIPT_MSG_TIMELINE);
}
break;
}
if (type == 'e') {
- WSContentSend_PD(SCRIPT_MSG_GTABLEbx, gc_str, chartindex);
+ WSContentSend_P(SCRIPT_MSG_GTABLEbx, gc_str, chartindex);
chartindex++;
return lp1;
}
@@ -9716,7 +9866,7 @@ exgc:
//Serial.printf("entries %d\n",entries);
if (gs_ctype=='T') {
if (anum && !(entries & 1)) {
- WSContentSend_PD(SCRIPT_MSG_GTABLEa);
+ WSContentSend_P(SCRIPT_MSG_GTABLEa);
char label[SCRIPT_MAXSSIZE];
lp = GetStringArgument(lp, OPER_EQU, label, 0);
SCRIPT_SKIP_SPACES
@@ -9724,9 +9874,9 @@ exgc:
lab2[0] = 0;
if (*lp!=')') {
lp = GetStringArgument(lp, OPER_EQU, lab2, 0);
- WSContentSend_PD(SCRIPT_MSG_GTABLEe);
+ WSContentSend_P(SCRIPT_MSG_GTABLEe);
} else {
- WSContentSend_PD(SCRIPT_MSG_GTABLEd);
+ WSContentSend_P(SCRIPT_MSG_GTABLEd);
}
for (uint32_t ind = 0; ind < anum; ind++) {
@@ -9747,34 +9897,34 @@ exgc:
}
for (uint32_t cnt = 0; cnt < ventries; cnt += 2) {
- WSContentSend_PD("['%s',",lbl);
+ WSContentSend_P("['%s',",lbl);
if (lab2[0]) {
- WSContentSend_PD("'%s',",lbl2);
+ WSContentSend_P("'%s',",lbl2);
}
uint32_t time = fp[cnt];
- WSContentSend_PD(SCRIPT_MSG_GOPT5, time / 60, time % 60);
- WSContentSend_PD(",");
+ WSContentSend_P(SCRIPT_MSG_GOPT5, time / 60, time % 60);
+ WSContentSend_P(",");
time = fp[cnt + 1];
- WSContentSend_PD(SCRIPT_MSG_GOPT5, time / 60, time % 60);
- WSContentSend_PD("]");
- if (cnt < ventries - 2) { WSContentSend_PD(","); }
+ WSContentSend_P(SCRIPT_MSG_GOPT5, time / 60, time % 60);
+ WSContentSend_P("]");
+ if (cnt < ventries - 2) { WSContentSend_P(","); }
}
if (ind < anum - 1) {
if (ventries) {
- WSContentSend_PD(",");
+ WSContentSend_P(",");
}
}
}
snprintf_P(options,sizeof(options), SCRIPT_MSG_GOPT4);
}
if (tonly) {
- WSContentSend_PD("]);");
+ WSContentSend_P("]);");
return lp1;
//goto nextwebline;
}
} else {
// we need to fetch the labels now
- WSContentSend_PD(SCRIPT_MSG_GTABLEa);
+ WSContentSend_P(SCRIPT_MSG_GTABLEa);
lp = gc_send_labels(lp, anum);
// now we have to export the values
@@ -9828,7 +9978,7 @@ exgc:
uint32_t aind = ipos;
if (aind >= entries) aind = entries - 1;
for (uint32_t cnt = 0; cnt < entries; cnt++) {
- WSContentSend_PD("['");
+ WSContentSend_P("['");
char lbl[16];
if (todflg >= 0) {
uint16_t mins = (float)(todflg % divflg) * (float)((float)60 / (float)divflg);
@@ -9856,8 +10006,8 @@ exgc:
sprintf(lbl, "%s-%02d", lbl, aind % divflg);
}
}
- WSContentSend_PD(lbl);
- WSContentSend_PD("',");
+ WSContentSend_P(lbl);
+ WSContentSend_P("',");
for (uint32_t ind = 0; ind < anum; ind++) {
char acbuff[32];
float *fp = arrays[ind];
@@ -9867,12 +10017,12 @@ exgc:
} else {
fval = fp[cnt];
}
- f2char(fval, glob_script_mem.script_dprec, glob_script_mem.script_lzero, acbuff);
- WSContentSend_PD("%s", acbuff);
- if (ind < anum - 1) { WSContentSend_PD(","); }
+ f2char(fval, glob_script_mem.script_dprec, glob_script_mem.script_lzero, acbuff, '.');
+ WSContentSend_P("%s", acbuff);
+ if (ind < anum - 1) { WSContentSend_P(","); }
}
- WSContentSend_PD("]");
- if (cnt < entries - 1) { WSContentSend_PD(","); }
+ WSContentSend_P("]");
+ if (cnt < entries - 1) { WSContentSend_P(","); }
aind++;
if (aind >= entries) {
aind = 0;
@@ -9880,7 +10030,7 @@ exgc:
}
// table complete
if (tonly) {
- WSContentSend_PD("]);");
+ WSContentSend_P("]);");
return lp1;
//goto nextwebline;
}
@@ -9949,19 +10099,19 @@ exgc:
(uint32_t)yellowFrom, (uint32_t)yellowTo);
}
}
- WSContentSend_PD(SCRIPT_MSG_GTABLEb, options);
- WSContentSend_PD(SCRIPT_MSG_GTABLEbx, gc_str, chartindex);
+ WSContentSend_P(SCRIPT_MSG_GTABLEb, options);
+ WSContentSend_P(SCRIPT_MSG_GTABLEbx, gc_str, chartindex);
chartindex++;
} else {
- WSContentSend_PD(PSTR("%s"), lin);
+ WSContentSend_P(PSTR("%s"), lin);
}
#else
if (!(specopt&WSO_FORCEMAIN)) {
lin++;
}
- WSContentSend_PD(PSTR("%s"), lin);
+ WSContentSend_P(PSTR("%s"), lin);
} else {
- // WSContentSend_PD(PSTR("%s"),lin);
+ // WSContentSend_P(PSTR("%s"),lin);
#endif //USE_GOOGLE_CHARTS
}
}
@@ -10954,7 +11104,7 @@ void script_add_subpage(uint8_t num) {
}
sprintf_P(id, PSTR("/sfd%1d"), num);
Webserver->on(id, wptr);
- WSContentSend_PD(HTTP_WEB_FULL_DISPLAY, num, bname);
+ WSContentSend_P(HTTP_WEB_FULL_DISPLAY, num, bname);
}
}
#endif // SCRIPT_FULL_WEBPAGE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_11_knx.ino b/tasmota/tasmota_xdrv_driver/xdrv_11_knx.ino
index c71a323e5..bb3d0ae71 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_11_knx.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_11_knx.ino
@@ -945,7 +945,7 @@ void HandleKNXConfiguration(void)
"var GA_AREA=eb('GA_AREA');"
"var GA_FDEF=eb('GA_FDEF');"
"if(GA_FNUM!=null&&GA_FNUM.value=='0'&&GA_AREA.value=='0'&&GA_FDEF.value=='0'){"
- "alert('" D_KNX_WARNING "');"
+ "alert(\"" D_KNX_WARNING "\");"
"}"
"}"
"function CBwarning()"
@@ -954,7 +954,7 @@ void HandleKNXConfiguration(void)
"var CB_AREA=eb('CB_AREA');"
"var CB_FDEF=eb('CB_FDEF');"
"if(CB_FNUM!=null&&CB_FNUM.value=='0'&&CB_AREA.value=='0'&&CB_FDEF.value=='0'){"
- "alert('" D_KNX_WARNING "');"
+ "alert(\"" D_KNX_WARNING "\");"
"}"
"}"));
WSContentSendStyle();
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
index 2b60c7e4d..99033c4e1 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
@@ -582,7 +582,6 @@ void DisplayText(void)
// pad field with spaces fxx
var = atoiv(cp, &fill);
cp += var;
- linebuf[fill] = 0;
break;
#ifdef USE_UFILESYS
case 'P':
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_14_mp3.ino b/tasmota/tasmota_xdrv_driver/xdrv_14_mp3.ino
index d139ffe06..88eff7dec 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_14_mp3.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_14_mp3.ino
@@ -176,12 +176,12 @@ uint16_t MP3_Checksum(uint8_t *array)
* define serial tx port fixed with 9600 baud
\*********************************************************************************************/
-void MP3PlayerInit(void)
+void MP3PlayerInit(void)
{
MP3Player = new TasmotaSerial(-1, Pin(GPIO_MP3_DFR562));
// start serial communication fixed to 9600 baud
- if (MP3Player->begin(9600))
+ if (MP3Player->begin(9600))
{
MP3Player->flush();
delay(1000);
@@ -192,7 +192,7 @@ void MP3PlayerInit(void)
}
if (PinUsed(GPIO_MP3_DFR562_BUSY)) // optional MP3 player busy pin...
- {
+ {
pinMode(Pin(GPIO_MP3_DFR562_BUSY), INPUT); // set pin to Input
}
@@ -208,10 +208,10 @@ void MP3PlayerInit(void)
* only track,play,stop and volume supported
\*********************************************************************************************/
-void MP3_SendCmd(uint8_t *scmd, uint8_t len)
+void MP3_SendCmd(uint8_t *scmd, uint8_t len)
{
uint16_t sum = 0;
- for (uint32_t cnt = 0; cnt < len; cnt++)
+ for (uint32_t cnt = 0; cnt < len; cnt++)
{
sum += scmd[cnt];
}
@@ -219,12 +219,12 @@ uint16_t sum = 0;
MP3Player->write(scmd, len + 1);
}
-void MP3_CMD(uint8_t mp3cmd, uint16_t val)
+void MP3_CMD(uint8_t mp3cmd, uint16_t val)
{
uint8_t scmd[8];
uint8_t len = 0;
scmd[0]=0xAA;
- switch (mp3cmd)
+ switch (mp3cmd)
{
case MP3_CMD_TRACK:
scmd[1]=0x07;
@@ -268,7 +268,7 @@ void MP3_CMD(uint8_t mp3cmd, uint16_t val)
* {0x7e , 0xff , 6 , 0 , 0/1 , 0 , 0 , 0 , 0 , 0xef };
\*********************************************************************************************/
-void MP3_CMD(uint8_t mp3cmd,uint16_t val)
+void MP3_CMD(uint8_t mp3cmd,uint16_t val)
{
uint8_t i = 0;
uint8_t cmd[10] = {0x7e,0xff,6,0,0,0,0,0,0,0xef}; // fill array
@@ -281,7 +281,7 @@ void MP3_CMD(uint8_t mp3cmd,uint16_t val)
cmd[8] = chks; // checksum low byte
MP3Player->write(cmd, sizeof(cmd)); // write mp3 data array to player
delay(1000);
- if (mp3cmd == MP3_CMD_RESET)
+ if (mp3cmd == MP3_CMD_RESET)
{
MP3_CMD(MP3_CMD_VOLUME, MP3_VOLUME); // after reset set volume depending on the entry in the my_user_config.h
}
@@ -293,17 +293,17 @@ void MP3_CMD(uint8_t mp3cmd,uint16_t val)
* check the MP3 commands
\*********************************************************************************************/
-bool MP3PlayerCmd(void)
+bool MP3PlayerCmd(void)
{
char command[CMDSZ];
bool serviced = true;
uint8_t disp_len = strlen(D_CMND_MP3);
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MP3), disp_len)) // prefix
- {
+ {
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMP3_Commands);
- switch (command_code)
+ switch (command_code)
{
case CMND_MP3_TRACK:
case CMND_MP3_FOLDER: // <-------- trl
@@ -312,7 +312,7 @@ bool MP3PlayerCmd(void)
case CMND_MP3_DEVICE:
case CMND_MP3_DAC:
// play a track, set volume, select EQ, specify file device
- if (XdrvMailbox.data_len > 0)
+ if (XdrvMailbox.data_len > 0)
{
if (command_code == CMND_MP3_TRACK) { MP3_CMD(MP3_CMD_TRACK, XdrvMailbox.payload); }
if (command_code == CMND_MP3_FOLDER) { MP3_CMD(MP3_CMD_FOLDER, XdrvMailbox.payload); } // <-------- trl
@@ -340,7 +340,7 @@ bool MP3PlayerCmd(void)
#ifdef USE_DY_SV17F
case CMND_MP3_PLAY:
- if (XdrvMailbox.data_len > 0)
+ if (XdrvMailbox.data_len > 0)
{
uint8_t scmd[64];
scmd[0] = 0xAA;
@@ -358,10 +358,10 @@ bool MP3PlayerCmd(void)
}
MP3_SendCmd(scmd, XdrvMailbox.data_len + 4);
Response_P(S_JSON_COMMAND_SVALUE, command, XdrvMailbox.data);
- } else
+ } else
{
MP3_CMD(MP3_CMD_PLAY, 0);
- Response_P(S_JSON_MP3_COMMAND, command, XdrvMailbox.payload)
+ Response_P(S_JSON_MP3_COMMAND, command, XdrvMailbox.payload);
}
break;
#endif // USE_DY_SV17F
@@ -371,7 +371,7 @@ bool MP3PlayerCmd(void)
serviced = false;
break;
}
- } else
+ } else
{
return false;
}
@@ -382,7 +382,7 @@ void MP3_EVERY_SECOND(void)
{
if (PinUsed(GPIO_MP3_DFR562_BUSY)) // optional MP3 player busy pin... // <-------- trl
- {
+ {
// Low is busy... we are using this format to allow Berry to receive a busy event
MP2BusyFlag = digitalRead(Pin(GPIO_MP3_DFR562_BUSY));
Response_P(PSTR("{\"MP3Player\":{\"MP3Busy\":%u}}"), MP2BusyFlag);
@@ -399,9 +399,9 @@ bool Xdrv14(uint8_t function)
{
bool result = false;
- if (PinUsed(GPIO_MP3_DFR562))
+ if (PinUsed(GPIO_MP3_DFR562))
{
- switch (function)
+ switch (function)
{
case FUNC_PRE_INIT:
MP3PlayerInit(); // init and start communication
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu.ino b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu.ino
index 12ee1e815..102f4374c 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu.ino
@@ -154,27 +154,29 @@ TuyaSend2 11,100 -> Sends integer (Type 2) data 100 to dpId 11 (Max data length
TuyaSend2 11,0xAABBCCDD -> Sends 4 bytes (Type 2) data to dpId 11 (Max data length 4 bytes)
TuyaSend3 11,ThisIsTheData -> Sends the supplied string (Type 3) to dpId 11 ( Max data length not-known)
TuyaSend4 11,1 -> Sends enum (Type 4) data 1 to dpId 11 (Max data length 1 bytes)
-TuyaSend5 11,ABCD -> Sends an HEX string (Type 3) data to dpId
+TuyaSend5 11,ABCD -> Sends an HEX string (Type 3) data to dpId
TuyaSend6 11,ABCD -> Sends raw (Type 0) data to dpId
*/
void CmndTuyaSend(void) {
- if (XdrvMailbox.index > 5 && XdrvMailbox.index < 8) {
+ if (XdrvMailbox.index > 6 && XdrvMailbox.index < 8) {
return;
}
if (XdrvMailbox.index == 0) {
TuyaRequestState(0);
+ ResponseCmndDone();
} else if (XdrvMailbox.index == 8) {
TuyaRequestState(8);
+ ResponseCmndDone();
} else if (XdrvMailbox.index == 9) { // TuyaSend Topic Toggle
Settings->tuyamcu_topic = !Settings->tuyamcu_topic;
AddLog(LOG_LEVEL_INFO, PSTR("TYA: TuyaMCU Stat Topic %s"), (Settings->tuyamcu_topic ? PSTR("enabled") : PSTR("disabled")));
-
+ ResponseCmndDone();
} else {
if (XdrvMailbox.data_len > 0) {
char *p;
- char *data;
+ char *data = nullptr;
uint8_t i = 0;
uint8_t dpId = 0;
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) {
@@ -186,22 +188,25 @@ void CmndTuyaSend(void) {
i++;
}
- if (1 == XdrvMailbox.index) {
- TuyaSendBool(dpId, strtoul(data, nullptr, 0));
- } else if (2 == XdrvMailbox.index) {
- TuyaSendValue(dpId, strtoull(data, nullptr, 0));
- } else if (3 == XdrvMailbox.index) {
- TuyaSendString(dpId, data);
- } else if (5 == XdrvMailbox.index) {
- TuyaSendHexString(dpId, data);
- } else if (4 == XdrvMailbox.index) {
- TuyaSendEnum(dpId, strtoul(data, nullptr, 0));
- } else if (6 == XdrvMailbox.index) {
- TuyaSendRaw(dpId, data);
+ if (data) {
+ if (1 == XdrvMailbox.index) {
+ TuyaSendBool(dpId, strtoul(data, nullptr, 0));
+ } else if (2 == XdrvMailbox.index) {
+ TuyaSendValue(dpId, strtoull(data, nullptr, 0));
+ } else if (3 == XdrvMailbox.index) {
+ TuyaSendString(dpId, data);
+ } else if (5 == XdrvMailbox.index) {
+ TuyaSendHexString(dpId, data);
+ } else if (4 == XdrvMailbox.index) {
+ TuyaSendEnum(dpId, strtoul(data, nullptr, 0));
+ } else if (6 == XdrvMailbox.index) {
+ TuyaSendRaw(dpId, data);
+ }
+ ResponseCmndDone();
}
}
}
- ResponseCmndDone();
+// {"TuyaSend":"error"}
}
// TuyaMcu fnid,dpid
@@ -517,7 +522,7 @@ static uint16_t convertHexStringtoBytes (uint8_t * dest, char src[], uint16_t sr
if (NULL == dest || NULL == src || 0 == src_len){
return 0;
}
-
+
char hexbyte[3];
hexbyte[2] = 0;
uint16_t i;
@@ -797,7 +802,7 @@ void TuyaProcessStatePacket(void) {
if (Tuya.buffer[dpidStart + 1] == 0) {
#ifdef USE_ENERGY_SENSOR
if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_POWER_COMBINED) {
- if (dpDataLen == 8) {
+ if (dpDataLen >= 8) {
uint16_t tmpVol = Tuya.buffer[dpidStart + 4] << 8 | Tuya.buffer[dpidStart + 5];
uint16_t tmpCur = Tuya.buffer[dpidStart + 7] << 8 | Tuya.buffer[dpidStart + 8];
uint16_t tmpPow = Tuya.buffer[dpidStart + 10] << 8 | Tuya.buffer[dpidStart + 11];
@@ -1301,13 +1306,13 @@ void TuyaSerialInput(void)
if (TuyaExcludeCMDsFromMQTT[cmdsID] == Tuya.buffer[3]) {
isCmdToSuppress = true;
break;
- }
+ }
}
if (!(isCmdToSuppress && Settings->flag5.tuya_exclude_from_mqtt)) { // SetOption137 - (Tuya) When Set, avoid the (MQTT-) publish of defined Tuya CMDs (see TuyaExcludeCMDsFromMQTT) if SetOption66 is active
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_TUYA_MCU_RECEIVED));
} else {
AddLog(LOG_LEVEL_DEBUG, ResponseData());
- }
+ }
} else {
AddLog(LOG_LEVEL_DEBUG, ResponseData());
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino b/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino
index e09f4fd22..734b0c0dd 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino
@@ -415,12 +415,14 @@ const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\
/********************************************************************************************/
-String GetHueDeviceId(uint16_t id)
+String GetHueDeviceId(uint16_t id, uint8_t ep = 0)
{
char s[32];
String deviceid = WiFi.macAddress();
deviceid.toLowerCase();
- snprintf(s, sizeof(s), "%s:%02x:11-%02x", deviceid.c_str(), (id >> 8) & 0xFF, id & 0xFF);
+ if (0x11 == ep) { ep = 0xFE; } // avoid collision with 0x11 which is used as default for `0`
+ if (0 == ep) { ep = 0x11; } // if ep is zero, revert to original value
+ snprintf(s, sizeof(s), "%s:%02x:%02X-%02x", deviceid.c_str(), (id >> 8) & 0xFF, ep, id & 0xFF);
return String(s); // 5c:cf:7f:13:9f:3d:00:11-01
}
@@ -619,10 +621,26 @@ void HueLightStatus2(uint8_t device, String *response)
}
#endif // USE_LIGHT
-// generate a unique lightId mixing local IP address and device number
-// it is limited to 32 devices.
-// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
-// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
+// generate a unique lightId mixing local mac address and device number
+//
+// Bits:
+// 0.. 3: low 4 bits of lightId - starting at 1 (encodes for 32+1..15 if bit 28 is 0, or 16..31 if bit 28 is 1)
+// 4..27: low 24 bits of mac address
+// 28: used to encode lightId 16..32
+// 29: zigbee device (1=zigbee, short address is encoded bit 15..0)
+// 31..32: unused, must be set to 0
+//
+// When in Zigbee mode (bit 29 == 1)
+// 0..15: short address of zigbee device
+// 16..23: endpoint on zigbee device (0..249), 0 means default endpoint (usually 1)
+// 24..28: unused, must be 0
+// 29: 1 (zigbee mode)
+// 31..32: unused, must be set to 0
+//
+// Parameters:
+// - relay_id: contains the lightId (1..32) or the Zigbee endpoint (0..250, 0 means default endpoint)
+// - z_shortaddr: Zigbee short addresses. Non-zero means Zigbee, zero means local (non-zigbee)
+//
uint32_t EncodeLightIdZigbee(uint8_t relay_id, uint16_t z_shortaddr)
{
uint8_t mac[6];
@@ -637,9 +655,9 @@ uint32_t EncodeLightIdZigbee(uint8_t relay_id, uint16_t z_shortaddr)
}
id |= (relay_id & 0xF);
#ifdef USE_ZIGBEE
- if ((z_shortaddr) && (!relay_id)) {
+ if (z_shortaddr) {
// fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
- id = (1 << 29) | z_shortaddr;
+ id = (1 << 29) | (relay_id << 16) | z_shortaddr;
}
#endif
@@ -651,11 +669,8 @@ uint32_t EncodeLightId(uint8_t relay_id)
return EncodeLightIdZigbee(relay_id, 0);
}
-// get hue_id and decode the relay_id
-// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
-// Zigbee:
-// If the Id encodes a Zigbee device (meaning bit 29 is set)
-// it returns 0 and sets the 'shortaddr' to the device short address
+// See above for encoding
+//
uint32_t DecodeLightIdZigbee(uint32_t hue_id, uint16_t * shortaddr)
{
uint8_t relay_id = hue_id & 0xF;
@@ -670,7 +685,7 @@ uint32_t DecodeLightIdZigbee(uint32_t hue_id, uint16_t * shortaddr)
if (hue_id & (1 << 29)) {
// this is actually a Zigbee ID
if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
- relay_id = 0;
+ relay_id = (hue_id >> 16) & 0xFF;
}
#endif // USE_ZIGBEE
return relay_id;
@@ -974,9 +989,9 @@ void HueLights(String *path_req)
device = DecodeLightId(device_id);
#ifdef USE_ZIGBEE
uint16_t shortaddr;
- device = DecodeLightIdZigbee(device_id, &shortaddr);
+ device = DecodeLightIdZigbee(device_id, &shortaddr); // device is endpoint when in Zigbee mode
if (shortaddr) {
- code = ZigbeeHandleHue(shortaddr, device_id, response);
+ code = ZigbeeHandleHue(shortaddr, device, device_id, device, response);
goto exit;
}
#endif // USE_ZIGBEE
@@ -1009,7 +1024,7 @@ void HueLights(String *path_req)
uint16_t shortaddr;
device = DecodeLightIdZigbee(device_id, &shortaddr);
if (shortaddr) {
- code = ZigbeeHueStatus(&response, shortaddr);
+ code = ZigbeeHueStatus(&response, shortaddr, device);
goto exit;
}
#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_0_constants.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_0_constants.ino
index ac2aae6ed..85090cfda 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_0_constants.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_0_constants.ino
@@ -923,6 +923,8 @@ enum Z_App_Profiles {
Z_PROF_TA = 0x0107, // Telecom Applications
Z_PROF_PHHC = 0x0108, // Personal Home & Hospital Care
Z_PROF_AMI = 0x0109, // Advanced Metering Initiative
+ // Green Power
+ Z_PROF_GP = 0xa1e0, // Green Power profile
};
enum Z_Device_Ids {
@@ -1129,6 +1131,46 @@ enum ZCL_Global_Commands {
ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d
};
+enum ZCL_GP_Commands_Received {
+ ZGP_NOTIFICATION = 0x00,
+ ZGP_PAIRING_SEARCH = 0x01,
+ ZGP_TUNNELING_STOP = 0x03,
+ ZGP_COMMISSIONING_NOTIFICATION = 0x04,
+ ZGP_TRANSLATION_TABLE_UPDATE = 0x07,
+ ZGP_TRANSLATION_TABLE_REQUEST = 0x08,
+ ZGP_PAIRING_CONFIGURATION = 0x09,
+};
+
+enum ZCL_GP_Commands_Generated {
+ ZGP_NOTIFICATION_RESPONSE = 0x00,
+ ZGP_PAIRING = 0x01,
+ ZGP_PROXY_COMMISSIONING_MODE = 0x02,
+ ZGP_RESPONSE = 0x06,
+ ZGP_TRANSLATION_TABLE_RESPONSE = 0x08,
+};
+
+enum ZCL_GPDF_Cmd {
+ ZGP_IDENTIFY = 0x00,
+ ZGP_SCENE_0 = 0x10,
+ // ...
+ ZGP_OFF = 0x20,
+ ZGP_ON = 0x21,
+ ZGP_TOGGLE = 0x22,
+ ZGP_RELEASE = 0x23,
+ ZGP_MOVE_UP = 0x30,
+ ZGP_MOVE_DOWN = 0x31,
+ //
+ //TODO
+ ZGP_COMMISSIONING = 0xE0,
+ ZGP_CHANNEL_REQUEST = 0xE3,
+ // Commands sent to GPD
+ ZGP_COMMISSIONING_REPLY = 0xF0,
+ ZGP_WRITE_ATTRIBUTES = 0xF1,
+ ZGP_READ_ATTRIBUTES = 0xF2,
+ ZGP_CHANNEL_CONFIGURATION = 0xF3,
+};
+
+
#define Z_(s) Zo_ ## s
// ZDP Enumeration, see Zigbee spec 2.4.5
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino
index 4cf5fae4e..4e67e4673 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino
@@ -88,43 +88,49 @@ enum class Za_type : uint8_t {
class Z_attribute {
public:
- // attribute key, either cluster+attribute_id or plain name
- union {
- struct {
- uint16_t cluster;
- uint16_t attr_id;
- } id;
- char * key;
- } key;
+ // cluster+attribute_id and/or plain name
+ uint16_t cluster; // only valid if both cluster and attr_id are not 0xFFFF
+ uint16_t attr_id; // only valid if both cluster and attr_id are not 0xFFFF
+ char* key; // only valid if `key_is_str` is true. Should not be freed if `key_is_pmem` is true
// attribute value
union {
uint32_t uval32;
int32_t ival32;
float fval;
SBuffer* bval;
- char* sval;
- class Z_attribute_list * objval;
- class JsonGeneratorArray * arrval;
+ char* sval; // string should be freed
+ class Z_attribute_list * objval; // contains a list of attributes, should be printed as a map (although it's just a list internally)
+ class JsonGeneratorArray * arrval; // contains a JSON string representing an array of values
} val;
Za_type type; // uint8_t in size, type of attribute, see above
bool key_is_str; // is the key a string?
bool key_is_pmem; // is the string in progmem, so we don't need to make a copy
bool val_str_raw; // if val is String, it is raw JSON and should not be escaped
+ bool key_is_cmd; // if command, cmd_id is the low 8 bits of attr_id.
+ // Bit #8 is `0` command sent to device or `1` command received from device
+ // Bit #9 is `0` command is cluster specific, or `1` general_command
uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1)
uint8_t attr_type; // [opt] type of the attribute, default to Zunk (0xFF)
- uint8_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change)
+ int8_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change)
+ int8_t attr_divider; // [opt] divider
+ uint16_t manuf; // manufacturer id (0 if none)
// Constructor with all defaults
Z_attribute():
- key{ .id = { 0x0000, 0x0000 } },
+ cluster(0xFFFF),
+ attr_id(0xFFFF),
+ key(nullptr),
val{ .uval32 = 0x0000 },
type(Za_type::Za_none),
key_is_str(false),
key_is_pmem(false),
val_str_raw(false),
+ key_is_cmd(false),
key_suffix(1),
attr_type(0xFF),
- attr_multiplier(1)
+ attr_multiplier(1),
+ attr_divider(1),
+ manuf(0)
{};
Z_attribute(const Z_attribute & rhs) {
@@ -156,6 +162,7 @@ public:
void setKeyName(const char * _key, const char * _key2);
void setKeyId(uint16_t cluster, uint16_t attr_id);
+ void setCmdId(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general);
// Setters
void setNone(void);
@@ -164,6 +171,7 @@ public:
void setInt(int32_t _val);
void setFloat(float _val);
+ void setRaw(const void* buf, size_t len);
void setBuf(const SBuffer &buf, size_t index, size_t len);
// specific formatters
@@ -201,7 +209,8 @@ public:
const char * getStr(void) const;
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const;
- bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
+ bool equalsId(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
+ bool equalsCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) const;
bool equalsKey(const char * name, uint8_t suffix = 0) const;
bool equalsVal(const Z_attribute & attr2) const;
bool equals(const Z_attribute & attr2) const;
@@ -263,6 +272,9 @@ public:
// Add attribute to the list, given cluster and attribute id
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
+ // ZCL command
+ Z_attribute & addAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0);
+
// Add attribute to the list, given name
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
@@ -282,12 +294,16 @@ public:
// find if attribute with same key already exists, return null if not found
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
+ const Z_attribute * findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
- const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis always count here
+ const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffix always count here
// non-const variants
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
}
+ inline Z_attribute * findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) {
+ return (Z_attribute*) ((const Z_attribute_list*)this)->findAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
+ }
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
}
@@ -302,6 +318,7 @@ public:
// if suffix == 0, we don't care and find the first match
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
+ Z_attribute & findOrCreateCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0);
// always care about suffix
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
// replace attribute with new value, suffix does care
@@ -324,8 +341,8 @@ Z_attribute & Z_attribute_list::addAttributePMEM(const char * name) {
// free any allocated memoruy for keys
void Z_attribute::freeKey(void) {
- if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
- key.key = nullptr;
+ if (key_is_str && key && !key_is_pmem) { delete[] key; }
+ key = nullptr;
}
// set key name
@@ -334,7 +351,7 @@ void Z_attribute::setKeyName(const char * _key, bool pmem) {
key_is_str = true;
key_is_pmem = pmem;
if (pmem) {
- key.key = (char*) _key;
+ key = (char*) _key;
} else {
setKeyName(_key, nullptr);
}
@@ -349,19 +366,28 @@ void Z_attribute::setKeyName(const char * _key, const char * _key2) {
if (_key2) {
key_len += strlen_P(_key2);
}
- key.key = new char[key_len+1];
- strcpy_P(key.key, _key);
+ key = new char[key_len+1];
+ strcpy_P(key, _key);
if (_key2) {
- strcat_P(key.key, _key2);
+ strcat_P(key, _key2);
}
}
}
-void Z_attribute::setKeyId(uint16_t cluster, uint16_t attr_id) {
+void Z_attribute::setKeyId(uint16_t _cluster, uint16_t _attr_id) {
freeKey();
key_is_str = false;
- key.id.cluster = cluster;
- key.id.attr_id = attr_id;
+ cluster = _cluster;
+ attr_id = _attr_id;
+ key_is_cmd = false;
+}
+
+void Z_attribute::setCmdId(uint16_t _cluster, uint8_t _cmd_id, bool direction, bool cmd_general) {
+ freeKey();
+ key_is_str = false;
+ cluster = _cluster;
+ attr_id = _cmd_id | (direction ? 0x100 : 0x000) | (cmd_general ? 0x200 : 0x000);
+ key_is_cmd = true;
}
// Setters
@@ -391,6 +417,15 @@ void Z_attribute::setFloat(float _val) {
type = Za_type::Za_float;
}
+void Z_attribute::setRaw(const void* buf, size_t len) {
+ freeVal();
+ if (len) {
+ val.bval = new SBuffer(len);
+ val.bval->addBuffer((const uint8_t*)buf, len);
+ }
+ type = Za_type::Za_raw;
+}
+
void Z_attribute::setBuf(const SBuffer &buf, size_t index, size_t len) {
freeVal();
if (len) {
@@ -512,10 +547,11 @@ bool Z_attribute::equalsKey(const Z_attribute & attr2, bool ignore_key_suffix) c
// check if keys are equal
if (key_is_str != attr2.key_is_str) { return false; }
if (key_is_str) {
- if (strcmp_PP(key.key, attr2.key.key)) { return false; }
+ if (strcmp_PP(key, attr2.key)) { return false; }
} else {
- if ((key.id.cluster != attr2.key.id.cluster) ||
- (key.id.attr_id != attr2.key.id.attr_id)) { return false; }
+ if ((this->cluster != attr2.cluster) ||
+ (this->attr_id != attr2.attr_id) ||
+ (this->key_is_cmd != attr2.key_is_cmd)) { return false; }
}
if (!ignore_key_suffix) {
if (key_suffix != attr2.key_suffix) { return false; }
@@ -523,14 +559,26 @@ bool Z_attribute::equalsKey(const Z_attribute & attr2, bool ignore_key_suffix) c
return true;
}
-bool Z_attribute::equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
- if (!key_is_str) {
- if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
- if (suffix) {
- if (key_suffix == suffix) { return true; }
- } else {
- return true;
- }
+bool Z_attribute::equalsId(uint16_t _cluster, uint16_t _attr_id, uint8_t suffix) const {
+ if (key_is_cmd || key_is_str) { return false; }
+ if ((this->cluster == _cluster) && (this->attr_id == _attr_id)) {
+ if (suffix) {
+ if (key_suffix == suffix) { return true; }
+ } else {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Z_attribute::equalsCmd(uint16_t _cluster, uint8_t _cmd_id, bool _direction, bool cmd_general, uint8_t suffix) const {
+ if (!key_is_cmd || key_is_str) { return false; }
+ uint16_t _attr_id = _cmd_id | (_direction ? 0x100 : 0x000) | (cmd_general ? 0x200 : 0x000);
+ if ((this->cluster == _cluster) && (this->attr_id == _attr_id)) {
+ if (suffix) {
+ if (key_suffix == suffix) { return true; }
+ } else {
+ return true;
}
}
return false;
@@ -538,7 +586,7 @@ bool Z_attribute::equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix)
bool Z_attribute::equalsKey(const char * name, uint8_t suffix) const {
if (key_is_str) {
- if (0 == strcmp_PP(key.key, name)) {
+ if (0 == strcmp_PP(key, name)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
@@ -578,14 +626,22 @@ String Z_attribute::toString(bool prefix_comma) const {
res += '"';
// compute the attribute name
if (key_is_str) {
- if (key.key) { res += EscapeJSONString(key.key); }
+ if (key) { res += EscapeJSONString(key); }
else { res += F("null"); } // shouldn't happen
if (key_suffix > 1) {
res += key_suffix;
}
} else {
char attr_name[12];
- snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
+ if (!key_is_cmd) { // regular attribute
+ snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), this->cluster, this->attr_id);
+ } else { // cmd
+ bool direction = (this->attr_id & 0x100);
+ bool cmd_general = (this->attr_id & 0x200);
+ uint8_t cmd_id = this->attr_id & 0xFF;
+ char cmd_char = cmd_general ? (direction ? '^' : '_') : (direction ? '?' : '!');
+ snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X%c%02X"), this->cluster, cmd_char, cmd_id);
+ }
res += attr_name;
if (key_suffix > 1) {
res += '+';
@@ -709,29 +765,30 @@ void Z_attribute::freeVal(void) {
}
void Z_attribute::deepCopy(const Z_attribute & rhs) {
- // copy key
- if (!rhs.key_is_str) {
- key.id.cluster = rhs.key.id.cluster;
- key.id.attr_id = rhs.key.id.attr_id;
- } else {
+ // copy cluster anymays
+ cluster = rhs.cluster;
+ attr_id = rhs.attr_id;
+ if (rhs.key_is_str) {
if (rhs.key_is_pmem) {
- key.key = rhs.key.key; // PMEM, don't copy
+ key = rhs.key; // PMEM, don't copy
} else {
- key.key = nullptr;
- if (rhs.key.key) {
- size_t key_len = strlen_P(rhs.key.key);
+ key = nullptr;
+ if (rhs.key) {
+ size_t key_len = strlen_P(rhs.key);
if (key_len) {
- key.key = new char[key_len+1];
- strcpy_P(key.key, rhs.key.key);
+ key = new char[key_len+1];
+ strcpy_P(key, rhs.key);
}
}
}
}
key_is_str = rhs.key_is_str;
key_is_pmem = rhs.key_is_pmem;
+ key_is_cmd = rhs.key_is_cmd;
key_suffix = rhs.key_suffix;
attr_type = rhs.attr_type;
attr_multiplier = rhs.attr_multiplier;
+ attr_divider = rhs.attr_divider;
// copy value
copyVal(rhs);
// don't touch next pointer
@@ -745,10 +802,21 @@ void Z_attribute::deepCopy(const Z_attribute & rhs) {
// add a cluster/attr_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute & attr = addToLast();
- attr.key.id.cluster = cluster;
- attr.key.id.attr_id = attr_id;
+ attr.cluster = cluster;
+ attr.attr_id = attr_id;
attr.key_is_str = false;
- if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.attr_id); }
+ if (!suffix) { attr.key_suffix = countAttribute(attr.cluster, attr.attr_id); }
+ else { attr.key_suffix = suffix; }
+ return attr;
+}
+
+// add a cluster/cmd_id attribute at the end of the list
+Z_attribute & Z_attribute_list::addAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) {
+ Z_attribute & attr = addToLast();
+ attr.cluster = cluster;
+ attr.attr_id = cmd_id | (direction ? 0x100 : 0) | (cmd_general ? 0x200 : 0x000);
+ attr.key_is_cmd = true;
+ if (!suffix) { attr.key_suffix = countAttribute(attr.cluster, attr.attr_id); }
else { attr.key_suffix = suffix; }
return attr;
}
@@ -757,7 +825,7 @@ Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id,
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, pmem);
- if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
+ if (!suffix) { attr.key_suffix = countAttribute(attr.key); }
else { attr.key_suffix = suffix; }
return attr;
}
@@ -765,7 +833,7 @@ Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, name2);
- if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
+ if (!suffix) { attr.key_suffix = countAttribute(attr.key); }
else { attr.key_suffix = suffix; }
return attr;
}
@@ -815,32 +883,46 @@ String Z_attribute_list::toString(bool enclose_brackets, bool include_battery) c
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
uint8_t suffix = attr.key_suffix;
if (attr.key_is_str) {
- return findAttribute(attr.key.key, suffix);
+ return findAttribute(attr.key, suffix);
+ } else if (!attr.key_is_cmd) {
+ return findAttribute(attr.cluster, attr.attr_id, suffix);
} else {
- return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, suffix);
+ return findAttributeCmd(attr.cluster, attr.attr_id & 0xFF, attr.attr_id & 0x100 ? true : false, attr.attr_id & 0x200 ? true : false, suffix);
}
}
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
for (const auto & attr : *this) {
- if (attr.equalsKey(cluster, attr_id, suffix)) { return &attr; }
+ if (attr.equalsId(cluster, attr_id, suffix) && !attr.key_is_cmd) { return &attr; }
}
return nullptr;
}
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
size_t count = 0;
for (const auto & attr : *this) {
- if (attr.equalsKey(cluster, attr_id, 0)) { count++; }
+ if (attr.equalsId(cluster, attr_id, 0)) { count++; }
}
return count;
}
+const Z_attribute * Z_attribute_list::findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) const {
+ for (const auto & attr : *this) {
+ if (attr.equalsCmd(cluster, cmd_id, direction, cmd_general, suffix)) { return &attr; }
+ }
+ return nullptr;
+}
+
// return the existing attribute or create a new one
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
return found ? *found : addAttribute(cluster, attr_id, suffix);
}
+Z_attribute & Z_attribute_list::findOrCreateCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) {
+ Z_attribute * found = findAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
+ return found ? *found : addAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
+}
+
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsKey(name, suffix)) { return &attr; }
@@ -862,8 +944,10 @@ Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t
// same but passing a Z_attribute as key
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
- Z_attribute & ret = attr.key_is_str ? findOrCreateAttribute(attr.key.key, attr.key_suffix)
- : findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
+ Z_attribute & ret = attr.key_is_str ? findOrCreateAttribute(attr.key, attr.key_suffix)
+ : attr.key_is_cmd ?
+ findOrCreateCmd(attr.cluster, attr.attr_id & 0xFF, attr.attr_id & 0x100 ? true : false, attr.attr_id & 0x200 ? true : false, attr.key_suffix)
+ : findOrCreateAttribute(attr.cluster, attr.attr_id, attr.key_suffix);
ret.key_suffix = attr.key_suffix;
return ret;
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino
index cf11e2405..548b4fe9b 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino
@@ -24,6 +24,91 @@
#endif
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
+// Convert a multiplier or divisor initially on 2 bytes, to a single byte
+// We use a property that values are usually powers of 10 or 2/5/25/50...
+// Values can range from 0 to 31e7
+typedef struct uint8log_t {
+ uint8_t mantissa : 6; // 0..63
+ uint8_t exponent10 : 2; // 0..3
+} uint8log_t;
+
+// convert uint8log to uint, lossless conversion, but can create an overflow with high exponent
+uint16_t uint8log_to_uint16(uint8_t v8) {
+ uint8log_t * v_log = (uint8log_t*) &v8;
+ uint32_t val = v_log->mantissa;
+ for (uint32_t i = 0; i < v_log->exponent10; i++) {
+ val = val * 10;
+ }
+ return val;
+}
+
+// convert uint16_t to uint8log, ther is potential rounding happening above 63, except when a multiple of 10
+uint8_t uint16_to_uint8log(uint16_t val) {
+ uint8log_t v_log;
+ uint32_t mantissa = val; // mantissa must be 0..63
+ uint32_t expo10 = 0; // exponent in base 10
+
+ while (mantissa > 63) {
+ expo10++;
+ mantissa = mantissa / 10;
+ }
+ // test overflow
+ if (expo10 > 3) {
+ expo10 = 3;
+ mantissa = 63; // max value is 63000
+ }
+ v_log.mantissa = mantissa;
+ v_log.exponent10 = expo10;
+ uint8_t * v8 = (uint8_t*) &v_log;
+ return *v8;
+}
+
+const uint16_t uint8log_test_vectors[] = {
+ 0,1,2,5,10,20,33,50,66,75,100,150,200,300,500,1000,2500,3000,5000,10000,20000,25000,30000,50000,60000,65535
+};
+
+// uint8log unit tests (normally not called)
+void uint8log_tests(void) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: sizeof(uint8log_t)=%i", sizeof(uint8log_t));
+
+ for (uint32_t i = 0; i < sizeof(uint8log_test_vectors)/2; i++) {
+ uint16_t v16 = uint8log_test_vectors[i];
+ uint8_t v = uint16_to_uint8log(v16);
+ uint16_t v16_out = uint8log_to_uint16(v);
+ AddLog(LOG_LEVEL_INFO, ">>>: v16=%5i out=%5i %s hex=0x%02X", v16, v16_out, v16 != v16_out ? "<>" : "", v);
+ }
+}
+/*
+Output:
+00:00:00.128 ZIG: sizeof(uint8log_t)=1
+00:00:00.129 >>>: v16= 0 out= 0 hex=0x00
+00:00:00.129 >>>: v16= 1 out= 1 hex=0x01
+00:00:00.130 >>>: v16= 2 out= 2 hex=0x02
+00:00:00.140 >>>: v16= 5 out= 5 hex=0x05
+00:00:00.140 >>>: v16= 10 out= 10 hex=0x0A
+00:00:00.151 >>>: v16= 20 out= 20 hex=0x14
+00:00:00.151 >>>: v16= 33 out= 33 hex=0x21
+00:00:00.152 >>>: v16= 50 out= 50 hex=0x32
+00:00:00.162 >>>: v16= 66 out= 60 <> hex=0x46
+00:00:00.163 >>>: v16= 75 out= 70 <> hex=0x47
+00:00:00.173 >>>: v16= 100 out= 100 hex=0x4A
+00:00:00.174 >>>: v16= 150 out= 150 hex=0x4F
+00:00:00.174 >>>: v16= 200 out= 200 hex=0x54
+00:00:00.185 >>>: v16= 300 out= 300 hex=0x5E
+00:00:00.185 >>>: v16= 500 out= 500 hex=0x72
+00:00:00.185 >>>: v16= 1000 out= 1000 hex=0x8A
+00:00:00.196 >>>: v16= 2500 out= 2500 hex=0x99
+00:00:00.196 >>>: v16= 3000 out= 3000 hex=0x9E
+00:00:00.207 >>>: v16= 5000 out= 5000 hex=0xB2
+00:00:00.207 >>>: v16=10000 out=10000 hex=0xCA
+00:00:00.208 >>>: v16=20000 out=20000 hex=0xD4
+00:00:00.219 >>>: v16=25000 out=25000 hex=0xD9
+00:00:00.219 >>>: v16=30000 out=30000 hex=0xDE
+00:00:00.219 >>>: v16=50000 out=50000 hex=0xF2
+00:00:00.230 >>>: v16=60000 out=60000 hex=0xFC
+00:00:00.231 >>>: v16=65535 out=63000 <> hex=0xFF
+*/
+
enum class Z_Data_Type : uint8_t {
Z_Unknown = 0x00,
Z_Light = 1, // Lights 1-5 channels
@@ -180,22 +265,50 @@ public:
Z_Data_Plug(uint8_t endpoint = 0) :
Z_Data(Z_Data_Type::Z_Plug, endpoint),
mains_voltage(0xFFFF),
- mains_power(-0x8000)
+ mains_power(-0x8000),
+ ac_voltage_div(1),
+ ac_voltage_mul(1),
+ ac_current_div(1),
+ ac_current_mul(1),
+ ac_power_div(1),
+ ac_power_mul(1)
{}
inline bool validMainsVoltage(void) const { return 0xFFFF != mains_voltage; }
inline bool validMainsPower(void) const { return -0x8000 != mains_power; }
- inline uint16_t getMainsVoltage(void) const { return mains_voltage; }
- inline int16_t getMainsPower(void) const { return mains_power; }
+ inline uint16_t getMainsVoltageRaw(void) const { return mains_voltage; }
+ inline int16_t getMainsPowerRaw(void) const { return mains_power; }
+ inline float getMainsVoltage(void) const { return (float)mains_voltage * getACVoltageMul() / getACVoltageDiv(); }
+ inline float getMainsPower(void) const { return (float)mains_power * getACPowerMul() / getACPowerDiv(); }
- inline void setMainsVoltage(uint16_t _mains_voltage) { mains_voltage = _mains_voltage; }
- inline void setMainsPower(int16_t _mains_power) { mains_power = _mains_power; }
+ inline void setMainsVoltageRaw(uint16_t _mains_voltage) { mains_voltage = _mains_voltage; }
+ inline void setMainsPowerRaw(int16_t _mains_power) { mains_power = _mains_power; }
+
+ inline uint16_t getACVoltageDiv(void) const { return uint8log_to_uint16(ac_voltage_div); }
+ inline uint16_t getACVoltageMul(void) const { return uint8log_to_uint16(ac_voltage_mul); }
+ inline uint16_t getACCurrentDiv(void) const { return uint8log_to_uint16(ac_current_div); }
+ inline uint16_t getACCurrentMul(void) const { return uint8log_to_uint16(ac_current_mul); }
+ inline uint16_t getACPowerDiv(void) const { return uint8log_to_uint16(ac_power_div); }
+ inline uint16_t getACPowerMul(void) const { return uint8log_to_uint16(ac_power_mul); }
+
+ inline void setACVoltageDiv(uint16_t v) { ac_voltage_div = uint16_to_uint8log(v); }
+ inline void setACVoltageMul(uint16_t v) { ac_voltage_mul = uint16_to_uint8log(v); }
+ inline void setACCurrentDiv(uint16_t v) { ac_current_div = uint16_to_uint8log(v); }
+ inline void setACCurrentMul(uint16_t v) { ac_current_mul = uint16_to_uint8log(v); }
+ inline void setACPowerDiv(uint16_t v) { ac_power_div = uint16_to_uint8log(v); }
+ inline void setACPowerMul(uint16_t v) { ac_power_mul = uint16_to_uint8log(v); }
static const Z_Data_Type type = Z_Data_Type::Z_Plug;
// 4 bytes
uint16_t mains_voltage; // AC voltage
int16_t mains_power; // Active power
+ uint8_t ac_voltage_div;
+ uint8_t ac_voltage_mul;
+ uint8_t ac_current_div;
+ uint8_t ac_current_mul;
+ uint8_t ac_power_div;
+ uint8_t ac_power_mul;
};
/*********************************************************************************************\
@@ -702,6 +815,114 @@ const Z_Data & Z_Data_Set::find(Z_Data_Type type, uint8_t ep) const {
return z_data_unk; // mark as unknown
}
+/*********************************************************************************************\
+ * Class used to store friendly names of endpoints
+\*********************************************************************************************/
+class Z_EP_Name {
+public:
+ Z_EP_Name() :
+ endpoint(0),
+ name(nullptr)
+ {}
+
+ inline const char * getName(void) const { return name != nullptr ? name : PSTR(""); }
+ void setName(const char *new_name);
+
+ ~Z_EP_Name() { if (name) free(name); }
+
+public:
+ uint8_t endpoint;
+ char * name;
+};
+
+
+class Z_EP_Name_list : public LList {
+public:
+
+ // INVARIANT: there is at most one entry for any `endpoint` value
+ // INVARIANT: if an entry exists, then the name is not null nor empty string
+
+ // we don't need explicit constructor, the superclass handles it
+
+ // add or change an ep name, or remove if set to empty string
+ void setEPName(uint8_t ep, const char * name) {
+ if (name == nullptr || strlen_P(name) == 0) {
+ this->removeEPName(ep);
+ return;
+ }
+ for (auto & epn : *this) {
+ if (epn.endpoint == ep) {
+ epn.setName(name);
+ return; // found it, exit
+ }
+ }
+ // ep not found, create it
+ Z_EP_Name & epn = this->addToLast();
+ epn.endpoint = ep;
+ epn.setName(name);
+ }
+
+ // remove ep name from list
+ void removeEPName(uint8_t ep) {
+ for (auto & epn : *this) {
+ if (epn.endpoint == ep) {
+ this->remove(&epn);
+ return; // found it, exit
+ }
+ }
+
+ }
+
+ // find a endpoint by name, or return 0 if not found
+ uint8_t findEPName(const char * name) const {
+ if (name == nullptr || strlen_P(name) == 0) { return 0; }
+ for (const auto & epn : *this) {
+ if (strcasecmp(epn.name, name) == 0) { return epn.endpoint; }
+ }
+ return 0; // not found
+ }
+
+ // get ep name, or return nullptr if none
+ const char * getEPName(uint8_t ep) const {
+ for (auto & epn : *this) {
+ if (epn.endpoint == ep) {
+ return epn.name;
+ }
+ }
+ return nullptr;
+ }
+
+ // Publish endpoint names if any as `"Names":{"2":"name2","3":"name3"}`
+ String tojson(void) const {
+ String s;
+ if (!this->isEmpty()) {
+ s += '{';
+ bool first = true;
+ for (const auto & epn : *this) {
+ if (!first) { s += ','; }
+ s += '"';
+ s += epn.endpoint;
+ s += F("\":\"");
+ s += EscapeJSONString(epn.name);
+ s += '"';
+ first = false;
+ }
+ s += '}';
+ }
+ return s;
+ }
+
+ // append to JSON
+ void ResponseAppend(void) const {
+ String s = tojson();
+ if (s.length() > 0) {
+ ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAMES "\":%s"), s.c_str());
+ }
+ }
+};
+
+
+
/*********************************************************************************************\
* Structures for Rules variables related to the last received message
\*********************************************************************************************/
@@ -719,6 +940,8 @@ public:
uint32_t defer_last_message_sent;
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
+ // List of names for endpoints
+ Z_EP_Name_list ep_names;
// Used for attribute reporting
Z_attribute_list attr_list;
// sequence number for Zigbee frames
@@ -734,7 +957,7 @@ public:
// other status - device wide data is 8 bytes
// START OF DEVICE WIDE DATA
uint32_t last_seen; // Last seen time (epoch)
- uint32_t batt_last_seen; // Time when we last received battery status (epoch), 0 means unknown, 0xFFFFFFFF means that the device has no battery
+ uint32_t batt_last_seen; // Time when we last received battery status (epoch), 0 means unknown, 0xFFFFFFFF means that the device has no battery, 0xFFFFFFFE means the device is Green Power, all values above 0xFFFFFFF0 are reserved
uint32_t batt_last_probed; // Time when the device was last probed for batteyr values
uint8_t lqi; // lqi from last message, 0xFF means unknown
uint8_t batt_percent; // battery percentage (0..100), 0xFF means unknwon
@@ -783,11 +1006,11 @@ public:
inline bool validLqi(void) const { return 0xFF != lqi; }
inline bool validBatteryPercent(void) const { return 0xFF != batt_percent; }
inline bool validLastSeen(void) const { return 0x0 != last_seen; }
- inline bool validBattLastSeen(void) const { return (0x0 != batt_last_seen) && (0xFFFFFFFF != batt_last_seen); }
+ inline bool validBattLastSeen(void) const { return (0x0 != batt_last_seen) && (batt_last_seen < 0xFFFFFFF0); }
inline void setReachable(bool _reachable) { reachable = _reachable; }
inline bool getReachable(void) const { return reachable; }
- inline bool getPower(uint8_t ep =0) const;
+ inline bool getPower(uint8_t ep = 0) const;
inline bool isRouter(void) const { return is_router; }
inline bool isCoordinator(void) const { return 0x0000 == shortaddr; }
@@ -806,21 +1029,26 @@ public:
}
}
inline void setHasNoBattery(void) { batt_last_seen = 0xFFFFFFFF; }
- inline bool hasNoBattery(void) const { return 0xFFFFFFFF == batt_last_seen; }
+ inline bool hasNoBattery(void) const { return batt_last_seen >= 0xFFFFFFF0; }
+ inline void setGP(void) { batt_last_seen = 0xFFFFFFFE; } // Green Power
+ inline bool isGP(void) const { return 0xFFFFFFFE == batt_last_seen; }
// Add an endpoint to a device
bool addEndpoint(uint8_t endpoint);
void clearEndpoints(void);
uint32_t countEndpoints(void) const; // return the number of known endpoints (0 if unknown)
+ bool setEPName(uint8_t ep, const char * name);
void setManufId(const char * str);
void setModelId(const char * str);
void setFriendlyName(const char * str);
+ void setFriendlyEPName(uint8_t ep, const char * str); // ability to have friendly names for endpoints
void setLastSeenNow(void);
// multiple function to dump part of the Device state into JSON
void jsonAddDeviceNamme(Z_attribute_list & attr_list) const;
+ void jsonAddEPName(Z_attribute_list & attr_list) const;
void jsonAddIEEE(Z_attribute_list & attr_list) const;
void jsonAddModelManuf(Z_attribute_list & attr_list) const;
void jsonAddEndpoints(Z_attribute_list & attr_list) const;
@@ -838,8 +1066,8 @@ public:
void setPower(bool power_on, uint8_t ep = 0);
// If light, returns the number of channels, or 0xFF if unknown
- int8_t getLightChannels(void) const {
- const Z_Data_Light & light = data.find(0);
+ int8_t getLightChannels(uint8_t ep = 0) const {
+ const Z_Data_Light & light = data.find(ep);
if (&light != &z_data_unk) {
return light.getConfig();
} else {
@@ -847,9 +1075,16 @@ public:
}
}
- void setLightChannels(int8_t channels);
-
-protected:
+ int8_t getHueBulbtype(uint8_t ep = 0) const {
+ int8_t light_profile = getLightChannels(ep);
+ if (0x00 == (light_profile & 0xF0)) {
+ return (light_profile & 0x07);
+ } else {
+ // not a bulb
+ return -1;
+ }
+ }
+ void setLightChannels(int8_t channels, uint8_t ep);
static void setStringAttribute(char*& attr, const char * str);
};
@@ -871,9 +1106,10 @@ typedef enum Z_Def_Category {
Z_CAT_PERMIT_JOIN, // timer to signal the end of the PermitJoin period
// Below will clear based on device + cluster pair.
Z_CLEAR_DEVICE_CLUSTER,
- Z_CAT_READ_CLUSTER,
+ // none for now
// Below will clear based on device + cluster + endpoint
Z_CLEAR_DEVICE_CLUSTER_ENDPOINT,
+ Z_CAT_READ_CLUSTER,
Z_CAT_EP_DESC, // read endpoint descriptor to gather clusters
Z_CAT_BIND, // send auto-binding to coordinator
Z_CAT_CONFIG_ATTR, // send a config attribute reporting request
@@ -917,7 +1153,7 @@ public:
// - 0x = the device's short address
Z_Device & isKnownLongAddrDevice(uint64_t longaddr) const;
Z_Device & isKnownIndexDevice(uint32_t index) const;
- Z_Device & isKnownFriendlyNameDevice(const char * name) const;
+ Z_Device & isKnownFriendlyNameDevice(const char * name, uint8_t * ep = nullptr) const;
Z_Device & findShortAddr(uint16_t shortaddr);
const Z_Device & findShortAddr(uint16_t shortaddr) const;
@@ -929,6 +1165,7 @@ public:
inline bool foundDevice(const Z_Device & device) const { return device.valid(); }
int32_t findFriendlyName(const char * name) const;
+ int32_t findFriendlyNameOrEPName(const char * name, uint8_t * ep) const;
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
uint8_t findFirstEndpoint(uint16_t shortaddr) const;
@@ -956,10 +1193,10 @@ public:
int32_t deviceRestore(JsonParserObject json);
// Hue support
- int8_t getHueBulbtype(uint16_t shortaddr) const ;
+ int8_t getHueBulbtype(uint16_t shortaddr, uint8_t ep = 0) const ;
void hideHueBulb(uint16_t shortaddr, bool hidden);
bool isHueBulbHidden(uint16_t shortaddr) const ;
- Z_Data_Light & getLight(uint16_t shortaddr);
+ Z_Data_Light & getLight(uint16_t shortaddr, uint8_t ep = 0);
// device is reachable
void deviceWasReached(uint16_t shortaddr);
@@ -995,7 +1232,7 @@ public:
void clean(void); // avoid writing to flash the last changes
// Find device by name, can be short_addr, long_addr, number_in_array or name
- Z_Device & parseDeviceFromName(const char * param, uint16_t * parsed_shortaddr = nullptr, int32_t mailbox_payload = 0);
+ Z_Device & parseDeviceFromName(const char * param, uint16_t * parsed_shortaddr = nullptr, uint8_t * ep = nullptr, int32_t mailbox_payload = 0);
bool isTuyaProtocol(uint16_t shortaddr, uint8_t ep = 0) const;
@@ -1015,7 +1252,7 @@ private:
* Berry support
\*********************************************************************************************/
#ifdef USE_BERRY
-extern "C" int32_t callBerryZigbeeDispatcher(const char* cmd, const char* type, void* data, int32_t idx);
+extern "C" int32_t callBerryZigbeeDispatcher(const char* event, const class ZCLFrame* zcl_frame, const class Z_attribute_list* attr_list, int32_t idx);
#endif // USE_BERRY
/*********************************************************************************************\
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino
index 02df96cf7..8d5a71fb5 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino
@@ -118,6 +118,37 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
return -1;
}
+//
+// Scan all devices to find a corresponding friendlyNme
+// Looks info device.friendlyName entry or the name of an endpoint
+// In:
+// friendlyName (null terminated, should not be empty)
+// Out:
+// index in _devices of entry, -1 if not found
+// ep == 0 means ep not found
+//
+int32_t Z_Devices::findFriendlyNameOrEPName(const char * name, uint8_t * ep) const {
+ if (ep) { *ep = 0; }
+ if (!name) { return -1; } // if pointer is null
+ size_t name_len = strlen(name);
+ int32_t found = 0;
+ if (name_len) {
+ for (auto &elem : _devices) {
+ if (elem.friendlyName) {
+ if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
+ }
+ uint8_t ep_found = elem.ep_names.findEPName(name);
+ if (ep_found) {
+ // found via ep name
+ if (ep) { *ep = ep_found; } // update ep
+ return found;
+ }
+ found++;
+ }
+ }
+ return -1;
+}
+
Z_Device & Z_Devices::isKnownLongAddrDevice(uint64_t longaddr) const {
return (Z_Device &) findLongAddr(longaddr);
}
@@ -130,10 +161,12 @@ Z_Device & Z_Devices::isKnownIndexDevice(uint32_t index) const {
}
}
-Z_Device & Z_Devices::isKnownFriendlyNameDevice(const char * name) const {
+Z_Device & Z_Devices::isKnownFriendlyNameDevice(const char * name, uint8_t * ep) const {
if ((!name) || (0 == strlen(name))) { return device_unk; } // Error
- int32_t found = findFriendlyName(name);
+ uint8_t ep_found;
+ int32_t found = findFriendlyNameOrEPName(name, &ep_found);
if (found >= 0) {
+ if (ep) { *ep = ep_found; }
return devicesAt(found);
} else {
return device_unk;
@@ -242,7 +275,7 @@ void Z_Device::clearEndpoints(void) {
// return true if a change was made
//
bool Z_Device::addEndpoint(uint8_t endpoint) {
- if ((0x00 == endpoint) || (endpoint > 240)) { return false; }
+ if ((0x00 == endpoint) || (endpoint > 240 && endpoint != 0xF2)) { return false; }
for (uint32_t i = 0; i < endpoints_max; i++) {
if (endpoint == endpoints[i]) {
@@ -276,8 +309,21 @@ uint8_t Z_Devices::findFirstEndpoint(uint16_t shortaddr) const {
return findShortAddr(shortaddr).endpoints[0]; // returns 0x00 if no endpoint
}
+// set a name to an endpoint, must exist in the list or return `false`
+bool Z_Device::setEPName(uint8_t ep, const char * name) {
+ if ((0x00 == ep) || (ep > 240 && ep != 0xF2)) { return false; }
+
+ for (uint32_t i = 0; i < endpoints_max; i++) {
+ if (ep == endpoints[i]) {
+ ep_names.setEPName(ep, name);
+ return true;
+ }
+ }
+ return false;
+}
+
void Z_Device::setStringAttribute(char*& attr, const char * str) {
- if (nullptr == str) { return; } // ignore a null parameter
+ if (nullptr == str) { str = PSTR(""); } // nullptr is considered empty string
size_t str_len = strlen(str);
if ((nullptr == attr) && (0 == str_len)) { return; } // if both empty, don't do anything
@@ -320,6 +366,15 @@ void Z_Device::setFriendlyName(const char * str) {
setStringAttribute(friendlyName, str);
}
+void Z_Device::setFriendlyEPName(uint8_t ep, const char * str) {
+ ep_names.setEPName(ep, str);
+}
+
+// needs to push the implementation here to use Z_Device static method
+void Z_EP_Name::setName(const char *new_name) {
+ Z_Device::setStringAttribute(name, new_name);
+}
+
void Z_Device::setLastSeenNow(void) {
// Only update time if after 2020-01-01 0000.
// Fixes issue where zigbee device pings before WiFi/NTP has set utc_time
@@ -358,38 +413,47 @@ uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
}
// returns: dirty flag, did we change the value of the object
-void Z_Device::setLightChannels(int8_t channels) {
- if (channels >= 0) {
+void Z_Device::setLightChannels(int8_t channels, uint8_t ep) {
+ if (channels >= 0) {
+ if (ep) { // if ep is not zero, the endpoint must exist
+ bool found = false;
+ for (uint32_t i = 0; i < endpoints_max; i++) {
+ if (ep == endpoints[i]) { found = true; break; }
+ }
+ if (!found) {
+ AddLog(LOG_LEVEL_INFO, D_LOG_ZIGBEE "cannot set light type to unknown ep=%i", ep);
+ return;
+ }
+ } else {
+ // if ep == 0, use first endpoint, or zero if no endpoint is known
+ ep = endpoints[0];
+ }
// retrieve of create light object
- Z_Data_Light & light = data.get(0);
+ Z_Data_Light & light = data.get(ep);
if (channels != light.getConfig()) {
light.setConfig(channels);
zigbee_devices.dirty();
}
- Z_Data_OnOff & onoff = data.get(0);
+ Z_Data_OnOff & onoff = data.get(ep);
(void)onoff;
} else {
// remove light / onoff object if any
for (auto & data_elt : data) {
if ((data_elt.getType() == Z_Data_Type::Z_Light) ||
(data_elt.getType() == Z_Data_Type::Z_OnOff)) {
- // remove light object
- data.remove(&data_elt);
- zigbee_devices.dirty();
+ if (ep == 0 || data_elt.getEndpoint() == ep) { // if remove ep==0 then remove all definitions
+ // remove light object
+ data.remove(&data_elt);
+ zigbee_devices.dirty();
+ }
}
}
}
}
-int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const {
+int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr, uint8_t ep) const {
const Z_Device &device = findShortAddr(shortaddr);
- int8_t light_profile = device.getLightChannels();
- if (0x00 == (light_profile & 0xF0)) {
- return (light_profile & 0x07);
- } else {
- // not a bulb
- return -1;
- }
+ return device.getHueBulbtype(ep);
}
void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
@@ -523,7 +587,13 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list
// internal function to publish device information with respect to all `SetOption`s
//
void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_list &attr_list, bool include_time) const {
- bool use_fname = (Settings->flag4.zigbee_use_names) && (friendlyName); // should we replace shortaddr with friendlyname?
+ const char * local_friendfly_name; // friendlyname publish can depend on the source endpoint
+ local_friendfly_name = ep_names.getEPName(attr_list.src_ep); // check if this ep has a specific name
+ if (local_friendfly_name == nullptr) {
+ // if no ep-specific name, get the device name
+ local_friendfly_name = friendlyName;
+ }
+ bool use_fname = (Settings->flag4.zigbee_use_names) && (local_friendfly_name); // should we replace shortaddr with friendlyname?
ResponseClear(); // clear string
@@ -541,7 +611,7 @@ void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_l
// What key do we use, shortaddr or name?
if (!Settings->flag5.zb_omit_json_addr) {
if (use_fname) {
- ResponseAppend_P(PSTR("{\"%s\":"), friendlyName);
+ ResponseAppend_P(PSTR("{\"%s\":"), local_friendfly_name);
} else {
ResponseAppend_P(PSTR("{\"0x%04X\":"), shortaddr);
}
@@ -551,11 +621,11 @@ void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_l
// Add "Device":"0x...."
ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), shortaddr);
// Add "Name":"xxx" if name is present
- if (friendlyName) {
- ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(friendlyName).c_str());
+ if (local_friendfly_name) {
+ ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(local_friendfly_name).c_str());
}
// Add all other attributes
- ResponseAppend_P(PSTR("%s}"), attr_list.toString(false, true).c_str()); // (false, true) - include battery
+ ResponseAppend_P(PSTR("%s}"), attr_list.toString(false).c_str());
if (!Settings->flag5.zb_omit_json_addr) {
ResponseAppend_P(PSTR("}"));
@@ -565,12 +635,16 @@ void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_l
ResponseAppend_P(PSTR("}"));
}
+#ifdef USE_INFLUXDB
+ InfluxDbProcess(1); // Use a copy of ResponseData
+#endif
+
if (Settings->flag4.zigbee_distinct_topics) {
char subtopic[TOPSZ];
- if (Settings->flag4.zb_topic_fname && friendlyName && strlen(friendlyName)) {
+ if (Settings->flag4.zb_topic_fname && local_friendfly_name && strlen(local_friendfly_name)) {
// Clean special characters
char stemp[TOPSZ];
- strlcpy(stemp, friendlyName, sizeof(stemp));
+ strlcpy(stemp, local_friendfly_name, sizeof(stemp));
MakeValidMqtt(0, stemp);
if (Settings->flag5.zigbee_hide_bridge_topic) {
snprintf_P(subtopic, sizeof(subtopic), PSTR("%s"), stemp);
@@ -642,12 +716,13 @@ void Z_Devices::clean(void) {
// - a friendly name, between quotes, example: "Room_Temp"
//
// In case the device is not found, the parsed 0x.... short address is passed to *parsed_shortaddr
-Z_Device & Z_Devices::parseDeviceFromName(const char * param, uint16_t * parsed_shortaddr, int32_t mailbox_payload) {
+Z_Device & Z_Devices::parseDeviceFromName(const char * param, uint16_t * parsed_shortaddr, uint8_t * ep, int32_t mailbox_payload) {
+ if (ep) { *ep = 0; } // mark as not found
if (nullptr == param) { return device_unk; }
size_t param_len = strlen(param);
char dataBuf[param_len + 1];
strcpy(dataBuf, param);
- RemoveSpace(dataBuf);
+ TrimSpace(dataBuf);
if (parsed_shortaddr != nullptr) { *parsed_shortaddr = BAD_SHORTADDR; } // if it goes wrong, mark as bad
if ((dataBuf[0] >= '0') && (dataBuf[0] <= '9') && (strlen(dataBuf) < 4)) {
@@ -671,7 +746,7 @@ Z_Device & Z_Devices::parseDeviceFromName(const char * param, uint16_t * parsed_
}
} else {
// expect a Friendly Name
- return isKnownFriendlyNameDevice(dataBuf);
+ return isKnownFriendlyNameDevice(dataBuf, ep);
}
}
@@ -692,6 +767,14 @@ void Z_Device::jsonAddDeviceNamme(Z_attribute_list & attr_list) const {
}
}
+// Add "Names":{"1":"name1","2":"name2"}
+void Z_Device::jsonAddEPName(Z_attribute_list & attr_list) const {
+ String s = ep_names.tojson();
+ if (s.length() > 0) {
+ attr_list.addAttributePMEM(PSTR(D_JSON_ZIGBEE_NAMES)).setStrRaw(s.c_str());
+ }
+}
+
// Add "IEEEAddr":"0x1234567812345678"
void Z_Device::jsonAddIEEE(Z_attribute_list & attr_list) const {
attr_list.addAttributePMEM(PSTR("IEEEAddr")).setHex64(longaddr);
@@ -791,6 +874,7 @@ void Z_Device::jsonDumpSingleDevice(Z_attribute_list & attr_list, uint32_t dump_
jsonAddDeviceNamme(attr_list);
}
if (dump_mode >= 2) {
+ jsonAddEPName(attr_list);
jsonAddIEEE(attr_list);
jsonAddModelManuf(attr_list);
jsonAddEndpoints(attr_list);
@@ -885,6 +969,22 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) {
}
}
+ // read "Names"
+ JsonParserToken val_names = json[PSTR("Names")];
+ if (val_names.isObject()) {
+ JsonParserObject attr_names = val_names.getObject();
+ // iterate on keys
+ for (auto key : attr_names) {
+ int32_t ep = key.getUInt();
+ if (ep > 255) { ep = 0; } // ep == 0 is invalid
+ const char * ep_name = key.getValue().getStr();
+ if (!ep || !device.setEPName(ep, ep_name)) {
+ AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ignoring ep=%i name='%s'"), ep, ep_name);
+ }
+ }
+ }
+
+
// read "Config"
JsonParserToken val_config = json[PSTR("Config")];
if (val_config.isArray()) {
@@ -910,8 +1010,8 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) {
return 0;
}
-Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) {
- return getShortAddr(shortaddr).data.get();
+Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr, uint8_t ep) {
+ return getShortAddr(shortaddr).data.get(ep);
}
bool Z_Devices::isTuyaProtocol(uint16_t shortaddr, uint8_t ep) const {
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4a_nano_fs.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4a_nano_fs.ino
index 6d2a64fc8..eb8e16969 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4a_nano_fs.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4a_nano_fs.ino
@@ -22,8 +22,9 @@
// #define Z_EEPROM_DEBUG
-const static uint32_t ZIGB_NAME1 = 0x3167697A; // 'zig1' little endian
+// const static uint32_t ZIGB_NAME1 = 0x3167697A; // 'zig1' little endian
const static uint32_t ZIGB_NAME2 = 0x3267697A; // 'zig2' little endian, v2
+const static uint32_t ZIGB_NAME4 = 0x3467697A; // 'zig4' little endian, v2
const static uint32_t ZIGB_DATA2 = 0x32746164; // 'dat2' little endian, v2
extern FS *dfsp;
extern "C" uint32_t _FS_end;
@@ -32,7 +33,7 @@ bool flash_valid(void) {
return (((uint32_t)&_FS_end) > 0x40280000) && (((uint32_t)&_FS_end) < 0x402FF000);
}
-void hydrateSingleDevice(const SBuffer & buf_d);
+void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version);
#ifdef USE_ZIGBEE_EEPROM
// The EEPROM is 64KB in size with individually writable bytes.
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4c_devices.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4c_devices.ino
index 70bfc53c4..259249696 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4c_devices.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4c_devices.ino
@@ -77,6 +77,32 @@
// uint8[] - list of configuration bytes, 0xFF marks the end
// i.e. 0xFF-0xFF marks the end of the array of endpoints
//
+// =======================
+// v4 which provides more extensibility
+// File structure: (all values are little Endian)
+//
+// uint8 - number of devices, 0xFF indicates invalid file (or erased Flash?)
+// [Array of devices, max to number of devices]
+// [starts at offset = 3]
+// uint16 - length of the device record, including the length field - allows to jump to next device
+
+// [mandatory device data]
+// uint16 - short address
+// uint64 - long IEEE address
+//
+// str - ModelID (null terminated C string, 32 chars max)
+// str - Manuf (null terminated C string, 32 chars max)
+// str - FriendlyName (null terminated C string, 32 chars max)
+//
+// [Array of endpoints]
+// uint8 - endpoint number, 0xFF marks the end of endpoints
+// uint8 - length of the endpoint information, excuding length byte and endpoint number
+// uint8[] - list of configuration bytes, 0xFF marks the end
+// str - (optional) FriendlyName for this endpoint (null terminated C string, 32 chars max), 0x00 if none
+//
+// [extended attributes]
+// : any other data until `length of the device record` is reached
+//
// Memory footprint
@@ -139,9 +165,9 @@ bool hibernateDeviceConfiguration(SBuffer & buf, const class Z_Data_Set & data,
* Only supports v2 (not the legacy old one long forgotten)
\*********************************************************************************************/
SBuffer hibernateDevice(const struct Z_Device &device) {
- SBuffer buf(128);
+ SBuffer buf(256);
- buf.add8(0x00); // overall length, will be updated later
+ buf.add16(0x0000); // overall length, will be updated later
buf.add16(device.shortaddr);
buf.add64(device.longaddr);
@@ -159,18 +185,36 @@ SBuffer hibernateDevice(const struct Z_Device &device) {
// check if we need to write fake endpoint 0x00
buf.add8(0x00);
+ uint32_t ep0_len_offset = buf.len(); // mark where to update the ep data length
+ buf.add8(0x00);
if (hibernateDeviceConfiguration(buf, device.data, 0)) {
buf.add8(0xFF); // end of configuration
+ buf.add8(0x00); // empty ep friendly name, as it would duplicate the global friendly name
+ // update the lenght of ep0_data_lenth
+ buf.set8(ep0_len_offset, buf.len() - ep0_len_offset - 1);
} else {
- buf.setLen(buf.len()-1); // remove 1 byte header
+ buf.setLen(buf.len()-2); // remove 2 bytes header
}
// scan endpoints
for (uint32_t i=0; i 32) { len = 32; } // max 32 chars
+ buf.addBuffer(ep_name, len);
+ buf.add8(0x00); // end of string marker
+ } else {
+ buf.add8(0x00); // no endpoint name
+ }
+ // update the lenght of ep0_data_lenth
+ buf.set8(ep_len_offset, buf.len() - ep_len_offset - 1);
}
buf.add8(0xFF); // end of endpoints
@@ -184,6 +228,8 @@ SBuffer hibernateDevice(const struct Z_Device &device) {
/*********************************************************************************************\
* Write Devices in EEPROM/File/Flash
*
+ * Updated to v4 format
+ *
* Writes the preamble and all devices in the Univ_Write_File structure.
* Does not close the file at the end.
* Returns true if succesful.
@@ -193,9 +239,10 @@ SBuffer hibernateDevice(const struct Z_Device &device) {
bool hibernateDevices(Univ_Write_File & write_data);
bool hibernateDevices(Univ_Write_File & write_data) {
// first prefix is number of devices
- uint8_t devices_size = zigbee_devices.devicesSize();
+ size_t devices_size = zigbee_devices.devicesSize();
if (devices_size > 250) { devices_size = 250; } // arbitrarily limit to 250 devices in EEPROM instead of 32 in Flash
- write_data.writeBytes(&devices_size, sizeof(devices_size));
+ uint8_t devices_size8 = devices_size;
+ write_data.writeBytes((uint8_t*)&devices_size8, sizeof(devices_size8)); // write number of devices in file
for (const auto & device : zigbee_devices.getDevices()) {
const SBuffer buf = hibernateDevice(device);
@@ -224,10 +271,10 @@ const char * hydrateSingleString(const SBuffer & buf, uint32_t *d) {
* hydrateSingleDevice
*
* Transforms a binary representation to a Zigbee device
- * Supports only v2
+ * Supports only v2 and v4
\*********************************************************************************************/
-void hydrateSingleDevice(const SBuffer & buf_d) {
- uint32_t d = 1; // index in device buffer
+void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version) {
+ uint32_t d = 0; // index in device buffer
uint16_t shortaddr = buf_d.get16(d); d += 2;
uint64_t longaddr = buf_d.get64(d); d += 8;
size_t buf_len = buf_d.len();
@@ -244,23 +291,52 @@ void hydrateSingleDevice(const SBuffer & buf_d) {
if (d >= buf_len) { return; }
- // Hue bulbtype - if present
- while (d < buf_len) {
+ // Read per-endpoint information
+ while (d < buf_len) {
uint8_t ep = buf_d.get8(d++);
if (0xFF == ep) { break; } // ep 0xFF marks the end of the endpoints
- if (ep > 240) { ep = 0xFF; } // ep == 0xFF means ignore
+ if (ep > 240 && ep != 0xF2) { ep = 0xFF; } // ep == 0xFF means ignore
device.addEndpoint(ep); // it will ignore invalid endpoints
- while (d < buf_len) {
- uint8_t config_type = buf_d.get8(d++);
- if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration
- uint8_t config = config_type & 0x0F;
- Z_Data_Type type = (Z_Data_Type) (config_type >> 4);
- // set the configuration
- if (ep != 0xFF) {
- Z_Data & z_data = device.data.getByType(type, ep);
- if (&z_data != nullptr) {
- z_data.setConfig(config);
- Z_Data_Set::updateData(z_data);
+
+ if (version == 4) {
+ if (d >= buf_len) { break; } // end of buffer
+ uint8_t ep_len = buf_d.get8(d++);
+ if (d + ep_len > buf_len) { break; } // buffer is too small to contain the announced data
+
+ while (d < buf_len) {
+ uint8_t config_type = buf_d.get8(d++);
+ if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration
+ uint8_t config = config_type & 0x0F;
+ Z_Data_Type type = (Z_Data_Type) (config_type >> 4);
+ // set the configuration
+ if (ep != 0xFF) {
+ Z_Data & z_data = device.data.getByType(type, ep);
+ if (&z_data != nullptr) {
+ z_data.setConfig(config);
+ Z_Data_Set::updateData(z_data);
+ }
+ }
+ }
+ // ability to have additional fields here
+ // friendly name for ep
+ if (d < buf_len) {
+ device.setFriendlyEPName(ep, hydrateSingleString(buf_d, &d));
+ }
+ // additional information comes here
+ } else {
+ // version == 2
+ while (d < buf_len) {
+ uint8_t config_type = buf_d.get8(d++);
+ if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration
+ uint8_t config = config_type & 0x0F;
+ Z_Data_Type type = (Z_Data_Type) (config_type >> 4);
+ // set the configuration
+ if (ep != 0xFF) {
+ Z_Data & z_data = device.data.getByType(type, ep);
+ if (&z_data != nullptr) {
+ z_data.setConfig(config);
+ Z_Data_Set::updateData(z_data);
+ }
}
}
}
@@ -277,33 +353,36 @@ void hydrateSingleDevice(const SBuffer & buf_d) {
bool loadZigbeeDevices(void) {
Univ_Read_File f; // universal reader
const char * storage_class = PSTR("");
-
-#ifdef USE_ZIGBEE_EEPROM
- if (zigbee.eeprom_ready) {
- f.init(ZIGB_NAME2);
- storage_class = PSTR("EEPROM");
- }
-#endif // USE_ZIGBEE_EEPROM
+ uint32_t file_version = 4; // currently supporting v3 and v4
#ifdef USE_UFILESYS
File file;
if (!f.valid() && dfsp) {
file = dfsp->open(TASM_FILE_ZIGBEE, "r");
+ if (!file) {
+ file = dfsp->open(TASM_FILE_ZIGBEE_LEGACY_V2, "r");
+ if (file) { file_version = 2; } // v2 found
+ }
if (file) {
- uint32_t signature = 0x0000;
- file.read((uint8_t*)&signature, 4);
- if (signature == ZIGB_NAME2) {
- // skip another 4 bytes
- file.read((uint8_t*)&signature, 4);
- } else {
- file.seek(0); // seek back to beginning of file
- }
f.init(&file);
storage_class = PSTR("File System");
}
}
#endif // USE_UFILESYS
+#ifdef USE_ZIGBEE_EEPROM
+ if (!f.valid() && zigbee.eeprom_ready) {
+ f.init(ZIGB_NAME4); // try v4 first
+ if (!f.valid()) {
+ f.init(ZIGB_NAME2); // else try v2
+ if (f.valid()) { file_version = 2; } // v2 found
+ }
+ if (f.valid()) {
+ storage_class = PSTR("EEPROM");
+ }
+ }
+#endif // USE_ZIGBEE_EEPROM
+
#ifdef ESP8266
if (!f.valid() && flash_valid()) {
// Read binary data from Flash
@@ -314,10 +393,10 @@ bool loadZigbeeDevices(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len);
// Check the signature
- if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2))
+ if ( ((flashdata.name == ZIGB_NAME2) || (flashdata.name == ZIGB_NAME4))
&& (flashdata.len > 0)) {
uint16_t buf_len = flashdata.len;
- // uint32_t version = (flashdata.name == ZIGB_NAME2) ? 2 : 1;
+ if (flashdata.name == ZIGB_NAME2) { file_version = 2; } // v2 found
f.init(z_dev_start + sizeof(Z_Flashentry), buf_len);
storage_class = PSTR("Flash");
}
@@ -338,26 +417,25 @@ bool loadZigbeeDevices(void) {
uint32_t k = 1; // byte index in global buffer
for (uint32_t i = 0; (i < num_devices) && (k < file_len); i++) {
- uint8_t dev_record_len = 0;
- f.readBytes(&dev_record_len, sizeof(dev_record_len));
- // int32_t ret = ZFS::readBytes(ZIGB_NAME2, &dev_record_len, 1, k, 1);
+ uint16_t dev_record_len = 0;
+ size_t dev_record_len_bytes = file_version >= 4 ? 2 : 1;
+ f.readBytes((uint8_t*)&dev_record_len, dev_record_len_bytes); // starting with v4, length is 2 bytes
if (dev_record_len == 0) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Invalid device information, aborting"));
zigbee_devices.clean(); // don't write back to Flash what we just loaded
return false;
}
- SBuffer buf(dev_record_len);
- buf.setLen(dev_record_len);
- buf.set8(0, dev_record_len); // push the first byte (len including this first byte)
- int32_t ret = f.readBytes(buf.buf(1), dev_record_len - 1);
- // ret = ZFS::readBytes(ZIGB_NAME2, buf.getBuffer(), dev_record_len, k, dev_record_len);
- if (ret != dev_record_len - 1) {
+ SBuffer buf(dev_record_len - dev_record_len_bytes);
+ buf.setLen(dev_record_len - dev_record_len_bytes);
+ buf.set8(0, dev_record_len - dev_record_len_bytes); // push the first byte (len including this first byte)
+ int32_t ret = f.readBytes(buf.buf(), dev_record_len - dev_record_len_bytes);
+ if (ret != dev_record_len - dev_record_len_bytes) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Invalid device information, aborting"));
zigbee_devices.clean(); // don't write back to Flash what we just loaded
return false;
}
- hydrateSingleDevice(buf);
+ hydrateSingleDevice(buf, file_version);
// next iteration
k += dev_record_len;
@@ -377,9 +455,10 @@ void saveZigbeeDevices(void) {
Univ_Write_File f;
const char * storage_class = PSTR("");
+// TODO can we prioritize filesystem instead of eeprom?
#ifdef USE_ZIGBEE_EEPROM
if (!f.valid() && zigbee.eeprom_ready) {
- f.init(ZIGB_NAME2);
+ f.init(ZIGB_NAME4);
storage_class = PSTR("EEPROM");
}
#endif
@@ -427,7 +506,7 @@ void saveZigbeeDevices(void) {
ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
Z_Flashentry *flashdata = (Z_Flashentry*)(spi_buffer + z_block_offset);
- flashdata->name = ZIGB_NAME2; // v2
+ flashdata->name = ZIGB_NAME4; // v4
flashdata->len = buf_len;
flashdata->start = 0;
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino
new file mode 100644
index 000000000..9320867e2
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino
@@ -0,0 +1,1603 @@
+/*
+ xdrv_23_zigbee_5__constants.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+// Below is a compilation of Strings used in Zigbee commands and converters.
+// Instead of using pointer to strings (4 bytes), we are using an offset (16 bits)
+// into an array of strings - which leads to a 1/3 more compact structure.
+
+// To generate the code below use https://tasmota.hadinger.fr/util
+// and copy/paste the entire arrays `Z_PostProcess` and `Z_Commands` concatenated
+// Note: the 'C' syntax is irrelevant, the parser only looks for `Z_()`
+
+// In addition the Python3 code used is below:
+
+/*Python code to generate code below
+
+import re
+pat = r"Z\(([^\)]+)\)" # extract text in Z() macro
+
+def clean(s):
+ return s.strip(" \t\n\r")
+
+def strings_to_pmem(arg):
+ #strings = arg.split("\n")
+ strings = re.findall(pat, arg)
+
+ # do some basic cleaning
+ strings_cleaned = [ clean(x) for x in strings if clean(x) != ""]
+
+ # remove duplicates
+ strings_cleaned = list(dict.fromkeys(strings_cleaned))
+
+ out_s = "const char Z_strings[] PROGMEM = \n"
+ out_i = "enum Z_offsets {\n"
+
+ index = 0;
+ # add a first empty string
+ out_s += " \"\\x00\"\n"
+ out_i += " Zo_ = " + str(index) + ",\n"
+ index += 1
+
+ for s in strings_cleaned:
+ out_s += " \"" + s + "\" \"\\x00\"\n"
+ out_i += " Zo_" + s + " = " + str(index) + ",\n"
+ index += len(s) + 1 # add one for null char
+
+ out_s += " \"\\x00\";"
+ out_i += "};"
+
+ return ("", out_s, out_i)
+
+
+*/
+
+/*
+ DO NOT EDIT
+*/
+
+const char Z_strings[] PROGMEM =
+ "\x00"
+ "00" "\x00"
+ "00190200" "\x00"
+ "00xx0A00" "\x00"
+ "00xxxx000000000000" "\x00"
+ "01" "\x00"
+ "0101" "\x00"
+ "01190200" "\x00"
+ "01xx0A00" "\x00"
+ "01xxxx" "\x00"
+ "01xxxx000000000000" "\x00"
+ "01xxxx0A0000000000" "\x00"
+ "03xx0A00" "\x00"
+ "03xxxx000000000000" "\x00"
+ "03xxxx0A0000000000" "\x00"
+ "ACActivePowerOverload" "\x00"
+ "ACAlarmsMask" "\x00"
+ "ACCurrentDivisor" "\x00"
+ "ACCurrentMultiplier" "\x00"
+ "ACCurrentOverload" "\x00"
+ "ACFrequency" "\x00"
+ "ACFrequencyDivisor" "\x00"
+ "ACFrequencyMax" "\x00"
+ "ACFrequencyMin" "\x00"
+ "ACFrequencyMultiplier" "\x00"
+ "ACPowerDivisor" "\x00"
+ "ACPowerMultiplier" "\x00"
+ "ACReactivePowerOverload" "\x00"
+ "ACVoltageDivisor" "\x00"
+ "ACVoltageMultiplier" "\x00"
+ "ACVoltageOverload" "\x00"
+ "AbsMaxCoolSetpointLimit" "\x00"
+ "AbsMaxHeatSetpointLimit" "\x00"
+ "AbsMinCoolSetpointLimit" "\x00"
+ "AbsMinHeatSetpointLimit" "\x00"
+ "AccelerationTimeLift" "\x00"
+ "ActiveCurrent" "\x00"
+ "ActiveCurrentPhB" "\x00"
+ "ActiveCurrentPhC" "\x00"
+ "ActivePower" "\x00"
+ "ActivePowerMax" "\x00"
+ "ActivePowerMaxPhB" "\x00"
+ "ActivePowerMaxPhC" "\x00"
+ "ActivePowerMin" "\x00"
+ "ActivePowerMinPhB" "\x00"
+ "ActivePowerMinPhC" "\x00"
+ "ActivePowerPhB" "\x00"
+ "ActivePowerPhC" "\x00"
+ "ActuatorEnabled" "\x00"
+ "AddGroup" "\x00"
+ "AddScene" "\x00"
+ "AlarmCount" "\x00"
+ "AlarmMask" "\x00"
+ "AnalogApplicationType" "\x00"
+ "AnalogDescription" "\x00"
+ "AnalogEngineeringUnits" "\x00"
+ "AnalogInApplicationType" "\x00"
+ "AnalogInDescription" "\x00"
+ "AnalogInEngineeringUnits" "\x00"
+ "AnalogInMaxValue" "\x00"
+ "AnalogInMinValue" "\x00"
+ "AnalogInOutOfService" "\x00"
+ "AnalogInReliability" "\x00"
+ "AnalogInResolution" "\x00"
+ "AnalogInStatusFlags" "\x00"
+ "AnalogOutApplicationType" "\x00"
+ "AnalogOutDescription" "\x00"
+ "AnalogOutEngineeringUnits" "\x00"
+ "AnalogOutMaxValue" "\x00"
+ "AnalogOutMinValue" "\x00"
+ "AnalogOutOfService" "\x00"
+ "AnalogOutOutOfService" "\x00"
+ "AnalogOutReliability" "\x00"
+ "AnalogOutRelinquishDefault" "\x00"
+ "AnalogOutResolution" "\x00"
+ "AnalogOutStatusFlags" "\x00"
+ "AnalogOutValue" "\x00"
+ "AnalogPriorityArray" "\x00"
+ "AnalogReliability" "\x00"
+ "AnalogRelinquishDefault" "\x00"
+ "AnalogStatusFlags" "\x00"
+ "AnalogValue" "\x00"
+ "AppVersion" "\x00"
+ "ApparentPower" "\x00"
+ "ApparentPowerPhB" "\x00"
+ "ApparentPowerPhC" "\x00"
+ "AqaraAccelerometer" "\x00"
+ "AqaraRotate" "\x00"
+ "AqaraVibration505" "\x00"
+ "AqaraVibrationMode" "\x00"
+ "AqaraVibrationsOrAngle" "\x00"
+ "Aqara_FF05" "\x00"
+ "ArrowClick" "\x00"
+ "ArrowHold" "\x00"
+ "ArrowRelease" "\x00"
+ "AutoRelockTime" "\x00"
+ "AvailablePower" "\x00"
+ "AverageRMSOverVoltage" "\x00"
+ "AverageRMSOverVoltageCounter" "\x00"
+ "AverageRMSOverVoltageCounterPhB" "\x00"
+ "AverageRMSOverVoltageCounterPhC" "\x00"
+ "AverageRMSUnderVoltage" "\x00"
+ "AverageRMSUnderVoltageCounter" "\x00"
+ "AverageRMSUnderVoltageCounterPhB" "\x00"
+ "AverageRMSUnderVoltageCounterPhC" "\x00"
+ "AverageRMSVoltageMeasurementPeriod" "\x00"
+ "AverageRMSVoltageMeasurementPeriodPhB" "\x00"
+ "AverageRMSVoltageMeasurementPeriodPhC" "\x00"
+ "BallastFactorAdjustment" "\x00"
+ "BallastLampQuantity" "\x00"
+ "BallastMaxLevel" "\x00"
+ "BallastMinLevel" "\x00"
+ "BallastPhysicalMaxLevel" "\x00"
+ "BallastPhysicalMinLevel" "\x00"
+ "BallastPowerOnFadeTime" "\x00"
+ "BallastPowerOnLevel" "\x00"
+ "BallastStatus" "\x00"
+ "BatteryAHrRating" "\x00"
+ "BatteryAlarmMask" "\x00"
+ "BatteryAlarmState" "\x00"
+ "BatteryManufacturer" "\x00"
+ "BatteryPercentage" "\x00"
+ "BatteryPercentageMinThreshold" "\x00"
+ "BatteryPercentageThreshold1" "\x00"
+ "BatteryPercentageThreshold2" "\x00"
+ "BatteryPercentageThreshold3" "\x00"
+ "BatteryQuantity" "\x00"
+ "BatteryRatedVoltage" "\x00"
+ "BatterySize" "\x00"
+ "BatteryVoltage" "\x00"
+ "BatteryVoltageMinThreshold" "\x00"
+ "BatteryVoltageThreshold1" "\x00"
+ "BatteryVoltageThreshold2" "\x00"
+ "BatteryVoltageThreshold3" "\x00"
+ "BinaryActiveText" "\x00"
+ "BinaryApplicationType" "\x00"
+ "BinaryDescription" "\x00"
+ "BinaryInActiveText" "\x00"
+ "BinaryInApplicationType" "\x00"
+ "BinaryInDescription" "\x00"
+ "BinaryInInactiveText" "\x00"
+ "BinaryInOutOfService" "\x00"
+ "BinaryInPolarity" "\x00"
+ "BinaryInReliability" "\x00"
+ "BinaryInStatusFlags" "\x00"
+ "BinaryInValue" "\x00"
+ "BinaryInactiveText" "\x00"
+ "BinaryMinimumOffTime" "\x00"
+ "BinaryMinimumOnTime" "\x00"
+ "BinaryOutActiveText" "\x00"
+ "BinaryOutApplicationType" "\x00"
+ "BinaryOutDescription" "\x00"
+ "BinaryOutInactiveText" "\x00"
+ "BinaryOutMinimumOffTime" "\x00"
+ "BinaryOutMinimumOnTime" "\x00"
+ "BinaryOutOfService" "\x00"
+ "BinaryOutOutOfService" "\x00"
+ "BinaryOutPolarity" "\x00"
+ "BinaryOutReliability" "\x00"
+ "BinaryOutRelinquishDefault" "\x00"
+ "BinaryOutStatusFlags" "\x00"
+ "BinaryOutValue" "\x00"
+ "BinaryReliability" "\x00"
+ "BinaryRelinquishDefault" "\x00"
+ "BinaryStatusFlags" "\x00"
+ "BinaryValue" "\x00"
+ "BlockedGPDID" "\x00"
+ "CIE" "\x00"
+ "CO" "\x00"
+ "CT" "\x00"
+ "CalculationPeriod" "\x00"
+ "CcommissioningExitMode" "\x00"
+ "CheckinInterval" "\x00"
+ "CheckinIntervalMin" "\x00"
+ "ClientActiveFunctionality" "\x00"
+ "ClientFunctionality" "\x00"
+ "ClosedLimit" "\x00"
+ "Color" "\x00"
+ "ColorCapabilities" "\x00"
+ "ColorLoopActive" "\x00"
+ "ColorLoopDirection" "\x00"
+ "ColorLoopStartEnhancedHue" "\x00"
+ "ColorLoopStoredEnhancedHue" "\x00"
+ "ColorLoopTime" "\x00"
+ "ColorMode" "\x00"
+ "ColorMove" "\x00"
+ "ColorPointBIntensity" "\x00"
+ "ColorPointBX" "\x00"
+ "ColorPointBY" "\x00"
+ "ColorPointGIntensity" "\x00"
+ "ColorPointGX" "\x00"
+ "ColorPointGY" "\x00"
+ "ColorPointRIntensity" "\x00"
+ "ColorPointRX" "\x00"
+ "ColorPointRY" "\x00"
+ "ColorStartUpColorTempireds" "\x00"
+ "ColorStep" "\x00"
+ "ColorTempMove" "\x00"
+ "ColorTempMoveDown" "\x00"
+ "ColorTempMoveStop" "\x00"
+ "ColorTempMoveUp" "\x00"
+ "ColorTempPhysicalMaxMireds" "\x00"
+ "ColorTempPhysicalMinMireds" "\x00"
+ "ColorTempStep" "\x00"
+ "ColorTempStepDown" "\x00"
+ "ColorTempStepUp" "\x00"
+ "CommissioningWindow" "\x00"
+ "CommunicationMode" "\x00"
+ "CompanyName" "\x00"
+ "CompensationText" "\x00"
+ "ConfigStatus" "\x00"
+ "Contact" "\x00"
+ "ControlSequenceOfOperation" "\x00"
+ "Coordinate1" "\x00"
+ "Coordinate2" "\x00"
+ "Coordinate3" "\x00"
+ "CurrentGroup" "\x00"
+ "CurrentPositionLift" "\x00"
+ "CurrentPositionLiftPercentage" "\x00"
+ "CurrentPositionTilt" "\x00"
+ "CurrentPositionTiltPercentage" "\x00"
+ "CurrentScene" "\x00"
+ "CurrentTemperature" "\x00"
+ "CurrentTemperatureSetPoint" "\x00"
+ "CurrentZoneSensitivityLevel" "\x00"
+ "CustomerName" "\x00"
+ "DCCurrent" "\x00"
+ "DCCurrentDivisor" "\x00"
+ "DCCurrentMax" "\x00"
+ "DCCurrentMin" "\x00"
+ "DCCurrentMultiplier" "\x00"
+ "DCCurrentOverload" "\x00"
+ "DCOverloadAlarmsMask" "\x00"
+ "DCPower" "\x00"
+ "DCPowerDivisor" "\x00"
+ "DCPowerMax" "\x00"
+ "DCPowerMin" "\x00"
+ "DCPowerMultiplier" "\x00"
+ "DCVoltage" "\x00"
+ "DCVoltageDivisor" "\x00"
+ "DCVoltageMax" "\x00"
+ "DCVoltageMin" "\x00"
+ "DCVoltageMultiplier" "\x00"
+ "DCVoltageOverload" "\x00"
+ "DataQualityID" "\x00"
+ "DateCode" "\x00"
+ "DecelerationTimeLift" "\x00"
+ "DefaultMoveRate" "\x00"
+ "DehumidificationCooling" "\x00"
+ "DehumidificationHysteresis" "\x00"
+ "DehumidificationLockout" "\x00"
+ "DehumidificationMaxCool" "\x00"
+ "DeviceEnabled" "\x00"
+ "DeviceTempAlarmMask" "\x00"
+ "Dimmer" "\x00"
+ "DimmerCurrentFrequency" "\x00"
+ "DimmerDown" "\x00"
+ "DimmerMaxFrequency" "\x00"
+ "DimmerMaxLevel" "\x00"
+ "DimmerMinFrequency" "\x00"
+ "DimmerMinLevel" "\x00"
+ "DimmerMove" "\x00"
+ "DimmerOptions" "\x00"
+ "DimmerRemainingTime" "\x00"
+ "DimmerStartUpLevel" "\x00"
+ "DimmerStep" "\x00"
+ "DimmerStepDown" "\x00"
+ "DimmerStepUp" "\x00"
+ "DimmerStop" "\x00"
+ "DimmerUp" "\x00"
+ "DisableLocalConfig" "\x00"
+ "DoorClosedEvents" "\x00"
+ "DoorOpenEvents" "\x00"
+ "DoorState" "\x00"
+ "DriftCompensation" "\x00"
+ "DstEnd" "\x00"
+ "DstShift" "\x00"
+ "DstStart" "\x00"
+ "ElectricalMeasurementType" "\x00"
+ "EnergyFormatting" "\x00"
+ "EnergyRemote" "\x00"
+ "EnergyTotal" "\x00"
+ "EnhancedColorMode" "\x00"
+ "EnhancedCurrentHue" "\x00"
+ "EurotronicErrors" "\x00"
+ "EurotronicHostFlags" "\x00"
+ "FanMode" "\x00"
+ "FanModeSequence" "\x00"
+ "FastPollTimeout" "\x00"
+ "FastPollTimeoutMax" "\x00"
+ "Fire" "\x00"
+ "FlowMaxMeasuredValue" "\x00"
+ "FlowMinMeasuredValue" "\x00"
+ "FlowRate" "\x00"
+ "FlowTolerance" "\x00"
+ "GPHueStop" "\x00"
+ "GPIdentify" "\x00"
+ "GPLevelStop" "\x00"
+ "GPLockDoor" "\x00"
+ "GPMoveColor" "\x00"
+ "GPMoveDown" "\x00"
+ "GPMoveDownOnOff" "\x00"
+ "GPMoveHueDown" "\x00"
+ "GPMoveHueUp" "\x00"
+ "GPMoveSatDown" "\x00"
+ "GPMoveSatUp" "\x00"
+ "GPMoveUp" "\x00"
+ "GPMoveUpOnOff" "\x00"
+ "GPOff" "\x00"
+ "GPOn" "\x00"
+ "GPPress1of1" "\x00"
+ "GPPress1of2" "\x00"
+ "GPPress2of2" "\x00"
+ "GPRelease" "\x00"
+ "GPRelease1of1" "\x00"
+ "GPRelease1of2" "\x00"
+ "GPRelease2of2" "\x00"
+ "GPSatStop" "\x00"
+ "GPScene0" "\x00"
+ "GPScene1" "\x00"
+ "GPScene10" "\x00"
+ "GPScene11" "\x00"
+ "GPScene12" "\x00"
+ "GPScene13" "\x00"
+ "GPScene14" "\x00"
+ "GPScene15" "\x00"
+ "GPScene2" "\x00"
+ "GPScene3" "\x00"
+ "GPScene4" "\x00"
+ "GPScene5" "\x00"
+ "GPScene6" "\x00"
+ "GPScene7" "\x00"
+ "GPScene8" "\x00"
+ "GPScene9" "\x00"
+ "GPShortPress1of1" "\x00"
+ "GPShortPress1of2" "\x00"
+ "GPShortPress2of2" "\x00"
+ "GPStepColor" "\x00"
+ "GPStepDown" "\x00"
+ "GPStepDownOnOff" "\x00"
+ "GPStepHueDown" "\x00"
+ "GPStepHueUp" "\x00"
+ "GPStepSatDown" "\x00"
+ "GPStepSatUp" "\x00"
+ "GPStepUp" "\x00"
+ "GPStepUpOnOff" "\x00"
+ "GPToggle" "\x00"
+ "GPUnlockDoor" "\x00"
+ "GenericDeviceClass" "\x00"
+ "GenericDeviceType" "\x00"
+ "GetAllGroups" "\x00"
+ "GetGroup" "\x00"
+ "GetSceneMembership" "\x00"
+ "GlassBreak" "\x00"
+ "GroupNameSupport" "\x00"
+ "HVACSystemTypeConfiguration" "\x00"
+ "HWVersion" "\x00"
+ "HarmonicCurrentMultiplier" "\x00"
+ "HighTempDwellTripPoint" "\x00"
+ "HighTempThreshold" "\x00"
+ "Hue" "\x00"
+ "HueMove" "\x00"
+ "HueSat" "\x00"
+ "HueStep" "\x00"
+ "HueStepDown" "\x00"
+ "HueStepUp" "\x00"
+ "Humidity" "\x00"
+ "HumidityMaxMeasuredValue" "\x00"
+ "HumidityMinMeasuredValue" "\x00"
+ "HumidityTolerance" "\x00"
+ "IASCIEAddress" "\x00"
+ "Identify" "\x00"
+ "IdentifyQuery" "\x00"
+ "IdentifyTime" "\x00"
+ "Illuminance" "\x00"
+ "IlluminanceLevelStatus" "\x00"
+ "IlluminanceLightSensorType" "\x00"
+ "IlluminanceMaxMeasuredValue" "\x00"
+ "IlluminanceMinMeasuredValue" "\x00"
+ "IlluminanceTargetLevel" "\x00"
+ "IlluminanceTolerance" "\x00"
+ "InstalledClosedLimitLift" "\x00"
+ "InstalledClosedLimitTilt" "\x00"
+ "InstalledOpenLimitLift" "\x00"
+ "InstalledOpenLimitTilt" "\x00"
+ "IntermediateSetpointsLift" "\x00"
+ "IntermediateSetpointsTilt" "\x00"
+ "IntrinsicBallastFactor" "\x00"
+ "LampAlarmMode" "\x00"
+ "LampBurnHours" "\x00"
+ "LampBurnHoursTripPoint" "\x00"
+ "LampManufacturer" "\x00"
+ "LampRatedHours" "\x00"
+ "LampType" "\x00"
+ "LastConfiguredBy" "\x00"
+ "LastMessageLQI" "\x00"
+ "LastMessageRSSI" "\x00"
+ "LastSetTime" "\x00"
+ "LegrandHeatingMode" "\x00"
+ "LegrandMode" "\x00"
+ "LegrandOpt1" "\x00"
+ "LegrandOpt2" "\x00"
+ "LegrandOpt3" "\x00"
+ "LidlPower" "\x00"
+ "LineCurrent" "\x00"
+ "LineCurrentPhB" "\x00"
+ "LineCurrentPhC" "\x00"
+ "LinkKey" "\x00"
+ "LocalTemperature" "\x00"
+ "LocalTemperatureCalibration" "\x00"
+ "LocalTime" "\x00"
+ "LocationAge" "\x00"
+ "LocationDescription" "\x00"
+ "LocationMethod" "\x00"
+ "LocationPower" "\x00"
+ "LocationType" "\x00"
+ "LockAlarmMask" "\x00"
+ "LockDefaultConfigurationRegister" "\x00"
+ "LockEnableInsideStatusLED" "\x00"
+ "LockEnableLocalProgramming" "\x00"
+ "LockEnableLogging" "\x00"
+ "LockEnableOneTouchLocking" "\x00"
+ "LockEnablePrivacyModeButton" "\x00"
+ "LockKeypadOperationEventMask" "\x00"
+ "LockKeypadProgrammingEventMask" "\x00"
+ "LockLEDSettings" "\x00"
+ "LockLanguage" "\x00"
+ "LockManualOperationEventMask" "\x00"
+ "LockOperatingMode" "\x00"
+ "LockRFIDOperationEventMask" "\x00"
+ "LockRFIDProgrammingEventMask" "\x00"
+ "LockRFOperationEventMask" "\x00"
+ "LockRFProgrammingEventMask" "\x00"
+ "LockSoundVolume" "\x00"
+ "LockState" "\x00"
+ "LockSupportedOperatingModes" "\x00"
+ "LockType" "\x00"
+ "LongPollInterval" "\x00"
+ "LongPollIntervalMin" "\x00"
+ "LowTempDwellTripPoint" "\x00"
+ "LowTempThreshold" "\x00"
+ "MainsAlarmMask" "\x00"
+ "MainsFrequency" "\x00"
+ "MainsVoltage" "\x00"
+ "MainsVoltageDwellTripPoint" "\x00"
+ "MainsVoltageMaxThreshold" "\x00"
+ "MainsVoltageMinThreshold" "\x00"
+ "Manufacturer" "\x00"
+ "MaxCoolSetpointLimit" "\x00"
+ "MaxHeatSetpointLimit" "\x00"
+ "MaxPINCodeLength" "\x00"
+ "MaxProxyTableEntries" "\x00"
+ "MaxRFIDCodeLength" "\x00"
+ "MaxSearchCounter" "\x00"
+ "MaxSinkTableEntries" "\x00"
+ "MaxTempExperienced" "\x00"
+ "Measured11thHarmonicCurrent" "\x00"
+ "Measured1stHarmonicCurrent" "\x00"
+ "Measured3rdHarmonicCurrent" "\x00"
+ "Measured5thHarmonicCurrent" "\x00"
+ "Measured7thHarmonicCurrent" "\x00"
+ "Measured9thHarmonicCurrent" "\x00"
+ "MeasuredPhase11thHarmonicCurrent" "\x00"
+ "MeasuredPhase1stHarmonicCurrent" "\x00"
+ "MeasuredPhase3rdHarmonicCurrent" "\x00"
+ "MeasuredPhase5thHarmonicCurrent" "\x00"
+ "MeasuredPhase7thHarmonicCurrent" "\x00"
+ "MeasuredPhase9thHarmonicCurrent" "\x00"
+ "MeterTypeID" "\x00"
+ "MinCoolSetpointLimit" "\x00"
+ "MinHeatSetpointLimit" "\x00"
+ "MinPINCodeLength" "\x00"
+ "MinRFIDCodeLength" "\x00"
+ "MinSetpointDeadBand" "\x00"
+ "MinTempExperienced" "\x00"
+ "Mode" "\x00"
+ "Model" "\x00"
+ "ModelId" "\x00"
+ "MotorStepSize" "\x00"
+ "Movement" "\x00"
+ "MullerLightMode" "\x00"
+ "MultiApplicationType" "\x00"
+ "MultiDescription" "\x00"
+ "MultiInApplicationType" "\x00"
+ "MultiInDescription" "\x00"
+ "MultiInNumberOfStates" "\x00"
+ "MultiInOutOfService" "\x00"
+ "MultiInReliability" "\x00"
+ "MultiInStatusFlags" "\x00"
+ "MultiInValue" "\x00"
+ "MultiNumberOfStates" "\x00"
+ "MultiOutApplicationType" "\x00"
+ "MultiOutDescription" "\x00"
+ "MultiOutNumberOfStates" "\x00"
+ "MultiOutOfService" "\x00"
+ "MultiOutOutOfService" "\x00"
+ "MultiOutReliability" "\x00"
+ "MultiOutRelinquishDefault" "\x00"
+ "MultiOutStatusFlags" "\x00"
+ "MultiOutValue" "\x00"
+ "MultiReliability" "\x00"
+ "MultiRelinquishDefault" "\x00"
+ "MultiStatusFlags" "\x00"
+ "MultiValue" "\x00"
+ "MultipleScheduling" "\x00"
+ "NeutralCurrent" "\x00"
+ "NotificationRetryNumber" "\x00"
+ "NotificationRetryTimer" "\x00"
+ "NumberOfDevices" "\x00"
+ "NumberOfHolidaySchedulesSupported" "\x00"
+ "NumberOfLogRecordsSupported" "\x00"
+ "NumberOfPINUsersSupported" "\x00"
+ "NumberOfPrimaries" "\x00"
+ "NumberOfRFIDUsersSupported" "\x00"
+ "NumberOfResets" "\x00"
+ "NumberOfTotalUsersSupported" "\x00"
+ "NumberOfWeekDaySchedulesSupportedPerUser" "\x00"
+ "NumberOfYearDaySchedulesSupportedPerUser" "\x00"
+ "NumberOfZoneSensitivityLevelsSupported" "\x00"
+ "NumberRSSIMeasurements" "\x00"
+ "NumberofActuationsLift" "\x00"
+ "NumberofActuationsTilt" "\x00"
+ "Occupancy" "\x00"
+ "OccupancySensorType" "\x00"
+ "OccupiedCoolingSetpoint" "\x00"
+ "OccupiedHeatingSetpoint" "\x00"
+ "OffTransitionTime" "\x00"
+ "OffWaitTime" "\x00"
+ "OnLevel" "\x00"
+ "OnOff" "\x00"
+ "OnOffTransitionTime" "\x00"
+ "OnTime" "\x00"
+ "OnTransitionTime" "\x00"
+ "OpenPeriod" "\x00"
+ "OppleMode" "\x00"
+ "OutdoorTemperature" "\x00"
+ "OverTempTotalDwell" "\x00"
+ "PICoolingDemand" "\x00"
+ "PIHeatingDemand" "\x00"
+ "PIROccupiedToUnoccupiedDelay" "\x00"
+ "PIRUnoccupiedToOccupiedDelay" "\x00"
+ "PIRUnoccupiedToOccupiedThreshold" "\x00"
+ "POD" "\x00"
+ "Panic" "\x00"
+ "PartNumber" "\x00"
+ "PathLossExponent" "\x00"
+ "PersistentMemoryWrites" "\x00"
+ "PersonalAlarm" "\x00"
+ "PhaseHarmonicCurrentMultiplier" "\x00"
+ "PhysicalClosedLimit" "\x00"
+ "PhysicalClosedLimitLift" "\x00"
+ "PhysicalClosedLimitTilt" "\x00"
+ "PhysicalEnvironment" "\x00"
+ "Power" "\x00"
+ "PowerDivisor" "\x00"
+ "PowerFactor" "\x00"
+ "PowerFactorPhB" "\x00"
+ "PowerFactorPhC" "\x00"
+ "PowerMultiplier" "\x00"
+ "PowerOffEffect" "\x00"
+ "PowerOnRecall" "\x00"
+ "PowerOnTimer" "\x00"
+ "PowerSource" "\x00"
+ "PowerThreshold" "\x00"
+ "Pressure" "\x00"
+ "PressureMaxMeasuredValue" "\x00"
+ "PressureMaxScaledValue" "\x00"
+ "PressureMinMeasuredValue" "\x00"
+ "PressureMinScaledValue" "\x00"
+ "PressureScale" "\x00"
+ "PressureScaledTolerance" "\x00"
+ "PressureScaledValue" "\x00"
+ "PressureTolerance" "\x00"
+ "Primary1Intensity" "\x00"
+ "Primary1X" "\x00"
+ "Primary1Y" "\x00"
+ "Primary2Intensity" "\x00"
+ "Primary2X" "\x00"
+ "Primary2Y" "\x00"
+ "Primary3Intensity" "\x00"
+ "Primary3X" "\x00"
+ "Primary3Y" "\x00"
+ "Primary4Intensity" "\x00"
+ "Primary4X" "\x00"
+ "Primary4Y" "\x00"
+ "Primary5Intensity" "\x00"
+ "Primary5X" "\x00"
+ "Primary5Y" "\x00"
+ "Primary6Intensity" "\x00"
+ "Primary6X" "\x00"
+ "Primary6Y" "\x00"
+ "ProductCode" "\x00"
+ "ProductRevision" "\x00"
+ "ProductURL" "\x00"
+ "ProxyTable" "\x00"
+ "QualityMeasure" "\x00"
+ "RGB" "\x00"
+ "RHDehumidificationSetpoint" "\x00"
+ "RMSCurrent" "\x00"
+ "RMSCurrentMax" "\x00"
+ "RMSCurrentMaxPhB" "\x00"
+ "RMSCurrentMaxPhC" "\x00"
+ "RMSCurrentMin" "\x00"
+ "RMSCurrentMinPhB" "\x00"
+ "RMSCurrentMinPhC" "\x00"
+ "RMSCurrentPhB" "\x00"
+ "RMSCurrentPhC" "\x00"
+ "RMSExtremeOverVoltage" "\x00"
+ "RMSExtremeOverVoltagePeriod" "\x00"
+ "RMSExtremeOverVoltagePeriodPhB" "\x00"
+ "RMSExtremeOverVoltagePeriodPhC" "\x00"
+ "RMSExtremeUnderVoltage" "\x00"
+ "RMSExtremeUnderVoltagePeriod" "\x00"
+ "RMSExtremeUnderVoltagePeriodPhB" "\x00"
+ "RMSExtremeUnderVoltagePeriodPhC" "\x00"
+ "RMSVoltage" "\x00"
+ "RMSVoltageMax" "\x00"
+ "RMSVoltageMaxPhB" "\x00"
+ "RMSVoltageMaxPhC" "\x00"
+ "RMSVoltageMin" "\x00"
+ "RMSVoltageMinPhB" "\x00"
+ "RMSVoltageMinPhC" "\x00"
+ "RMSVoltagePhB" "\x00"
+ "RMSVoltagePhC" "\x00"
+ "RMSVoltageSag" "\x00"
+ "RMSVoltageSagPeriod" "\x00"
+ "RMSVoltageSagPeriodPhB" "\x00"
+ "RMSVoltageSagPeriodPhC" "\x00"
+ "RMSVoltageSwell" "\x00"
+ "RMSVoltageSwellPeriod" "\x00"
+ "RMSVoltageSwellPeriodPhB" "\x00"
+ "RMSVoltageSwellPeriodPhC" "\x00"
+ "ReactiveCurrent" "\x00"
+ "ReactiveCurrentPhB" "\x00"
+ "ReactiveCurrentPhC" "\x00"
+ "ReactivePower" "\x00"
+ "ReactivePowerPhB" "\x00"
+ "ReactivePowerPhC" "\x00"
+ "RecallScene" "\x00"
+ "RelativeHumidity" "\x00"
+ "RelativeHumidityDisplay" "\x00"
+ "RelativeHumidityMode" "\x00"
+ "RemainingTime" "\x00"
+ "RemoteSensing" "\x00"
+ "RemoveAllGroups" "\x00"
+ "RemoveAllScenes" "\x00"
+ "RemoveGroup" "\x00"
+ "RemoveScene" "\x00"
+ "ReportingPeriod" "\x00"
+ "ResetAlarm" "\x00"
+ "ResetAllAlarms" "\x00"
+ "SWBuildID" "\x00"
+ "Sat" "\x00"
+ "SatMove" "\x00"
+ "SatStep" "\x00"
+ "SceneCount" "\x00"
+ "SceneNameSupport" "\x00"
+ "SceneValid" "\x00"
+ "ScheduleMode" "\x00"
+ "SeaPressure" "\x00"
+ "SecurityLevel" "\x00"
+ "ServerActiveFunctionality" "\x00"
+ "ServerFunctionality" "\x00"
+ "SharedSecurityKey" "\x00"
+ "SharedSecurityKeyType" "\x00"
+ "ShortPollInterval" "\x00"
+ "Shutter" "\x00"
+ "ShutterClose" "\x00"
+ "ShutterLift" "\x00"
+ "ShutterOpen" "\x00"
+ "ShutterStop" "\x00"
+ "ShutterTilt" "\x00"
+ "SinkTable" "\x00"
+ "SoftwareRevision" "\x00"
+ "StackVersion" "\x00"
+ "StandardTime" "\x00"
+ "StartUpOnOff" "\x00"
+ "Status" "\x00"
+ "StoreScene" "\x00"
+ "SwitchActions" "\x00"
+ "SwitchType" "\x00"
+ "SystemMode" "\x00"
+ "TRVBoost" "\x00"
+ "TRVChildProtection" "\x00"
+ "TRVMirrorDisplay" "\x00"
+ "TRVMode" "\x00"
+ "TRVWindowOpen" "\x00"
+ "TempTarget" "\x00"
+ "Temperature" "\x00"
+ "TemperatureDisplayMode" "\x00"
+ "TemperatureMaxMeasuredValue" "\x00"
+ "TemperatureMinMeasuredValue" "\x00"
+ "TemperatureTolerance" "\x00"
+ "TerncyDuration" "\x00"
+ "TerncyRotate" "\x00"
+ "ThSetpoint" "\x00"
+ "ThermostatAlarmMask" "\x00"
+ "ThermostatKeypadLockout" "\x00"
+ "ThermostatOccupancy" "\x00"
+ "ThermostatRunningMode" "\x00"
+ "ThermostatScheduleProgrammingVisibility" "\x00"
+ "Time" "\x00"
+ "TimeEpoch" "\x00"
+ "TimeStatus" "\x00"
+ "TimeZone" "\x00"
+ "TotalActivePower" "\x00"
+ "TotalApparentPower" "\x00"
+ "TotalProfileNum" "\x00"
+ "TotalReactivePower" "\x00"
+ "TuyaCalibration" "\x00"
+ "TuyaCalibrationTime" "\x00"
+ "TuyaMCUVersion" "\x00"
+ "TuyaMotorReversal" "\x00"
+ "TuyaMovingState" "\x00"
+ "TuyaQuery" "\x00"
+ "UnoccupiedCoolingSetpoint" "\x00"
+ "UnoccupiedHeatingSetpoint" "\x00"
+ "UtilityName" "\x00"
+ "ValidUntilTime" "\x00"
+ "ValvePosition" "\x00"
+ "VelocityLift" "\x00"
+ "ViewGroup" "\x00"
+ "ViewScene" "\x00"
+ "Water" "\x00"
+ "WhitePointX" "\x00"
+ "WhitePointY" "\x00"
+ "WindowCoveringType" "\x00"
+ "X" "\x00"
+ "Y" "\x00"
+ "ZCLVersion" "\x00"
+ "ZoneID" "\x00"
+ "ZoneState" "\x00"
+ "ZoneStatus" "\x00"
+ "ZoneStatusChange" "\x00"
+ "ZoneType" "\x00"
+ "_" "\x00"
+ "xx" "\x00"
+ "xx000A00" "\x00"
+ "xx0A" "\x00"
+ "xx0A00" "\x00"
+ "xx19" "\x00"
+ "xx190A" "\x00"
+ "xx190A00" "\x00"
+ "xxxx" "\x00"
+ "xxxx00" "\x00"
+ "xxxx0A00" "\x00"
+ "xxxxyy" "\x00"
+ "xxxxyyyy" "\x00"
+ "xxxxyyyy0A00" "\x00"
+ "xxxxyyzz" "\x00"
+ "xxyy" "\x00"
+ "xxyy0A00" "\x00"
+ "xxyyyy" "\x00"
+ "xxyyyy000000000000" "\x00"
+ "xxyyyy0A0000000000" "\x00"
+ "xxyyyyzz" "\x00"
+ "xxyyyyzzzz" "\x00"
+ "xxyyzzzz" "\x00"
+ ;
+enum Z_offsets {
+ Zo_ = 0,
+ Zo_00 = 1,
+ Zo_00190200 = 4,
+ Zo_00xx0A00 = 13,
+ Zo_00xxxx000000000000 = 22,
+ Zo_01 = 41,
+ Zo_0101 = 44,
+ Zo_01190200 = 49,
+ Zo_01xx0A00 = 58,
+ Zo_01xxxx = 67,
+ Zo_01xxxx000000000000 = 74,
+ Zo_01xxxx0A0000000000 = 93,
+ Zo_03xx0A00 = 112,
+ Zo_03xxxx000000000000 = 121,
+ Zo_03xxxx0A0000000000 = 140,
+ Zo_ACActivePowerOverload = 159,
+ Zo_ACAlarmsMask = 181,
+ Zo_ACCurrentDivisor = 194,
+ Zo_ACCurrentMultiplier = 211,
+ Zo_ACCurrentOverload = 231,
+ Zo_ACFrequency = 249,
+ Zo_ACFrequencyDivisor = 261,
+ Zo_ACFrequencyMax = 280,
+ Zo_ACFrequencyMin = 295,
+ Zo_ACFrequencyMultiplier = 310,
+ Zo_ACPowerDivisor = 332,
+ Zo_ACPowerMultiplier = 347,
+ Zo_ACReactivePowerOverload = 365,
+ Zo_ACVoltageDivisor = 389,
+ Zo_ACVoltageMultiplier = 406,
+ Zo_ACVoltageOverload = 426,
+ Zo_AbsMaxCoolSetpointLimit = 444,
+ Zo_AbsMaxHeatSetpointLimit = 468,
+ Zo_AbsMinCoolSetpointLimit = 492,
+ Zo_AbsMinHeatSetpointLimit = 516,
+ Zo_AccelerationTimeLift = 540,
+ Zo_ActiveCurrent = 561,
+ Zo_ActiveCurrentPhB = 575,
+ Zo_ActiveCurrentPhC = 592,
+ Zo_ActivePower = 609,
+ Zo_ActivePowerMax = 621,
+ Zo_ActivePowerMaxPhB = 636,
+ Zo_ActivePowerMaxPhC = 654,
+ Zo_ActivePowerMin = 672,
+ Zo_ActivePowerMinPhB = 687,
+ Zo_ActivePowerMinPhC = 705,
+ Zo_ActivePowerPhB = 723,
+ Zo_ActivePowerPhC = 738,
+ Zo_ActuatorEnabled = 753,
+ Zo_AddGroup = 769,
+ Zo_AddScene = 778,
+ Zo_AlarmCount = 787,
+ Zo_AlarmMask = 798,
+ Zo_AnalogApplicationType = 808,
+ Zo_AnalogDescription = 830,
+ Zo_AnalogEngineeringUnits = 848,
+ Zo_AnalogInApplicationType = 871,
+ Zo_AnalogInDescription = 895,
+ Zo_AnalogInEngineeringUnits = 915,
+ Zo_AnalogInMaxValue = 940,
+ Zo_AnalogInMinValue = 957,
+ Zo_AnalogInOutOfService = 974,
+ Zo_AnalogInReliability = 995,
+ Zo_AnalogInResolution = 1015,
+ Zo_AnalogInStatusFlags = 1034,
+ Zo_AnalogOutApplicationType = 1054,
+ Zo_AnalogOutDescription = 1079,
+ Zo_AnalogOutEngineeringUnits = 1100,
+ Zo_AnalogOutMaxValue = 1126,
+ Zo_AnalogOutMinValue = 1144,
+ Zo_AnalogOutOfService = 1162,
+ Zo_AnalogOutOutOfService = 1181,
+ Zo_AnalogOutReliability = 1203,
+ Zo_AnalogOutRelinquishDefault = 1224,
+ Zo_AnalogOutResolution = 1251,
+ Zo_AnalogOutStatusFlags = 1271,
+ Zo_AnalogOutValue = 1292,
+ Zo_AnalogPriorityArray = 1307,
+ Zo_AnalogReliability = 1327,
+ Zo_AnalogRelinquishDefault = 1345,
+ Zo_AnalogStatusFlags = 1369,
+ Zo_AnalogValue = 1387,
+ Zo_AppVersion = 1399,
+ Zo_ApparentPower = 1410,
+ Zo_ApparentPowerPhB = 1424,
+ Zo_ApparentPowerPhC = 1441,
+ Zo_AqaraAccelerometer = 1458,
+ Zo_AqaraRotate = 1477,
+ Zo_AqaraVibration505 = 1489,
+ Zo_AqaraVibrationMode = 1507,
+ Zo_AqaraVibrationsOrAngle = 1526,
+ Zo_Aqara_FF05 = 1549,
+ Zo_ArrowClick = 1560,
+ Zo_ArrowHold = 1571,
+ Zo_ArrowRelease = 1581,
+ Zo_AutoRelockTime = 1594,
+ Zo_AvailablePower = 1609,
+ Zo_AverageRMSOverVoltage = 1624,
+ Zo_AverageRMSOverVoltageCounter = 1646,
+ Zo_AverageRMSOverVoltageCounterPhB = 1675,
+ Zo_AverageRMSOverVoltageCounterPhC = 1707,
+ Zo_AverageRMSUnderVoltage = 1739,
+ Zo_AverageRMSUnderVoltageCounter = 1762,
+ Zo_AverageRMSUnderVoltageCounterPhB = 1792,
+ Zo_AverageRMSUnderVoltageCounterPhC = 1825,
+ Zo_AverageRMSVoltageMeasurementPeriod = 1858,
+ Zo_AverageRMSVoltageMeasurementPeriodPhB = 1893,
+ Zo_AverageRMSVoltageMeasurementPeriodPhC = 1931,
+ Zo_BallastFactorAdjustment = 1969,
+ Zo_BallastLampQuantity = 1993,
+ Zo_BallastMaxLevel = 2013,
+ Zo_BallastMinLevel = 2029,
+ Zo_BallastPhysicalMaxLevel = 2045,
+ Zo_BallastPhysicalMinLevel = 2069,
+ Zo_BallastPowerOnFadeTime = 2093,
+ Zo_BallastPowerOnLevel = 2116,
+ Zo_BallastStatus = 2136,
+ Zo_BatteryAHrRating = 2150,
+ Zo_BatteryAlarmMask = 2167,
+ Zo_BatteryAlarmState = 2184,
+ Zo_BatteryManufacturer = 2202,
+ Zo_BatteryPercentage = 2222,
+ Zo_BatteryPercentageMinThreshold = 2240,
+ Zo_BatteryPercentageThreshold1 = 2270,
+ Zo_BatteryPercentageThreshold2 = 2298,
+ Zo_BatteryPercentageThreshold3 = 2326,
+ Zo_BatteryQuantity = 2354,
+ Zo_BatteryRatedVoltage = 2370,
+ Zo_BatterySize = 2390,
+ Zo_BatteryVoltage = 2402,
+ Zo_BatteryVoltageMinThreshold = 2417,
+ Zo_BatteryVoltageThreshold1 = 2444,
+ Zo_BatteryVoltageThreshold2 = 2469,
+ Zo_BatteryVoltageThreshold3 = 2494,
+ Zo_BinaryActiveText = 2519,
+ Zo_BinaryApplicationType = 2536,
+ Zo_BinaryDescription = 2558,
+ Zo_BinaryInActiveText = 2576,
+ Zo_BinaryInApplicationType = 2595,
+ Zo_BinaryInDescription = 2619,
+ Zo_BinaryInInactiveText = 2639,
+ Zo_BinaryInOutOfService = 2660,
+ Zo_BinaryInPolarity = 2681,
+ Zo_BinaryInReliability = 2698,
+ Zo_BinaryInStatusFlags = 2718,
+ Zo_BinaryInValue = 2738,
+ Zo_BinaryInactiveText = 2752,
+ Zo_BinaryMinimumOffTime = 2771,
+ Zo_BinaryMinimumOnTime = 2792,
+ Zo_BinaryOutActiveText = 2812,
+ Zo_BinaryOutApplicationType = 2832,
+ Zo_BinaryOutDescription = 2857,
+ Zo_BinaryOutInactiveText = 2878,
+ Zo_BinaryOutMinimumOffTime = 2900,
+ Zo_BinaryOutMinimumOnTime = 2924,
+ Zo_BinaryOutOfService = 2947,
+ Zo_BinaryOutOutOfService = 2966,
+ Zo_BinaryOutPolarity = 2988,
+ Zo_BinaryOutReliability = 3006,
+ Zo_BinaryOutRelinquishDefault = 3027,
+ Zo_BinaryOutStatusFlags = 3054,
+ Zo_BinaryOutValue = 3075,
+ Zo_BinaryReliability = 3090,
+ Zo_BinaryRelinquishDefault = 3108,
+ Zo_BinaryStatusFlags = 3132,
+ Zo_BinaryValue = 3150,
+ Zo_BlockedGPDID = 3162,
+ Zo_CIE = 3175,
+ Zo_CO = 3179,
+ Zo_CT = 3182,
+ Zo_CalculationPeriod = 3185,
+ Zo_CcommissioningExitMode = 3203,
+ Zo_CheckinInterval = 3226,
+ Zo_CheckinIntervalMin = 3242,
+ Zo_ClientActiveFunctionality = 3261,
+ Zo_ClientFunctionality = 3287,
+ Zo_ClosedLimit = 3307,
+ Zo_Color = 3319,
+ Zo_ColorCapabilities = 3325,
+ Zo_ColorLoopActive = 3343,
+ Zo_ColorLoopDirection = 3359,
+ Zo_ColorLoopStartEnhancedHue = 3378,
+ Zo_ColorLoopStoredEnhancedHue = 3404,
+ Zo_ColorLoopTime = 3431,
+ Zo_ColorMode = 3445,
+ Zo_ColorMove = 3455,
+ Zo_ColorPointBIntensity = 3465,
+ Zo_ColorPointBX = 3486,
+ Zo_ColorPointBY = 3499,
+ Zo_ColorPointGIntensity = 3512,
+ Zo_ColorPointGX = 3533,
+ Zo_ColorPointGY = 3546,
+ Zo_ColorPointRIntensity = 3559,
+ Zo_ColorPointRX = 3580,
+ Zo_ColorPointRY = 3593,
+ Zo_ColorStartUpColorTempireds = 3606,
+ Zo_ColorStep = 3633,
+ Zo_ColorTempMove = 3643,
+ Zo_ColorTempMoveDown = 3657,
+ Zo_ColorTempMoveStop = 3675,
+ Zo_ColorTempMoveUp = 3693,
+ Zo_ColorTempPhysicalMaxMireds = 3709,
+ Zo_ColorTempPhysicalMinMireds = 3736,
+ Zo_ColorTempStep = 3763,
+ Zo_ColorTempStepDown = 3777,
+ Zo_ColorTempStepUp = 3795,
+ Zo_CommissioningWindow = 3811,
+ Zo_CommunicationMode = 3831,
+ Zo_CompanyName = 3849,
+ Zo_CompensationText = 3861,
+ Zo_ConfigStatus = 3878,
+ Zo_Contact = 3891,
+ Zo_ControlSequenceOfOperation = 3899,
+ Zo_Coordinate1 = 3926,
+ Zo_Coordinate2 = 3938,
+ Zo_Coordinate3 = 3950,
+ Zo_CurrentGroup = 3962,
+ Zo_CurrentPositionLift = 3975,
+ Zo_CurrentPositionLiftPercentage = 3995,
+ Zo_CurrentPositionTilt = 4025,
+ Zo_CurrentPositionTiltPercentage = 4045,
+ Zo_CurrentScene = 4075,
+ Zo_CurrentTemperature = 4088,
+ Zo_CurrentTemperatureSetPoint = 4107,
+ Zo_CurrentZoneSensitivityLevel = 4134,
+ Zo_CustomerName = 4162,
+ Zo_DCCurrent = 4175,
+ Zo_DCCurrentDivisor = 4185,
+ Zo_DCCurrentMax = 4202,
+ Zo_DCCurrentMin = 4215,
+ Zo_DCCurrentMultiplier = 4228,
+ Zo_DCCurrentOverload = 4248,
+ Zo_DCOverloadAlarmsMask = 4266,
+ Zo_DCPower = 4287,
+ Zo_DCPowerDivisor = 4295,
+ Zo_DCPowerMax = 4310,
+ Zo_DCPowerMin = 4321,
+ Zo_DCPowerMultiplier = 4332,
+ Zo_DCVoltage = 4350,
+ Zo_DCVoltageDivisor = 4360,
+ Zo_DCVoltageMax = 4377,
+ Zo_DCVoltageMin = 4390,
+ Zo_DCVoltageMultiplier = 4403,
+ Zo_DCVoltageOverload = 4423,
+ Zo_DataQualityID = 4441,
+ Zo_DateCode = 4455,
+ Zo_DecelerationTimeLift = 4464,
+ Zo_DefaultMoveRate = 4485,
+ Zo_DehumidificationCooling = 4501,
+ Zo_DehumidificationHysteresis = 4525,
+ Zo_DehumidificationLockout = 4552,
+ Zo_DehumidificationMaxCool = 4576,
+ Zo_DeviceEnabled = 4600,
+ Zo_DeviceTempAlarmMask = 4614,
+ Zo_Dimmer = 4634,
+ Zo_DimmerCurrentFrequency = 4641,
+ Zo_DimmerDown = 4664,
+ Zo_DimmerMaxFrequency = 4675,
+ Zo_DimmerMaxLevel = 4694,
+ Zo_DimmerMinFrequency = 4709,
+ Zo_DimmerMinLevel = 4728,
+ Zo_DimmerMove = 4743,
+ Zo_DimmerOptions = 4754,
+ Zo_DimmerRemainingTime = 4768,
+ Zo_DimmerStartUpLevel = 4788,
+ Zo_DimmerStep = 4807,
+ Zo_DimmerStepDown = 4818,
+ Zo_DimmerStepUp = 4833,
+ Zo_DimmerStop = 4846,
+ Zo_DimmerUp = 4857,
+ Zo_DisableLocalConfig = 4866,
+ Zo_DoorClosedEvents = 4885,
+ Zo_DoorOpenEvents = 4902,
+ Zo_DoorState = 4917,
+ Zo_DriftCompensation = 4927,
+ Zo_DstEnd = 4945,
+ Zo_DstShift = 4952,
+ Zo_DstStart = 4961,
+ Zo_ElectricalMeasurementType = 4970,
+ Zo_EnergyFormatting = 4996,
+ Zo_EnergyRemote = 5013,
+ Zo_EnergyTotal = 5026,
+ Zo_EnhancedColorMode = 5038,
+ Zo_EnhancedCurrentHue = 5056,
+ Zo_EurotronicErrors = 5075,
+ Zo_EurotronicHostFlags = 5092,
+ Zo_FanMode = 5112,
+ Zo_FanModeSequence = 5120,
+ Zo_FastPollTimeout = 5136,
+ Zo_FastPollTimeoutMax = 5152,
+ Zo_Fire = 5171,
+ Zo_FlowMaxMeasuredValue = 5176,
+ Zo_FlowMinMeasuredValue = 5197,
+ Zo_FlowRate = 5218,
+ Zo_FlowTolerance = 5227,
+ Zo_GPHueStop = 5241,
+ Zo_GPIdentify = 5251,
+ Zo_GPLevelStop = 5262,
+ Zo_GPLockDoor = 5274,
+ Zo_GPMoveColor = 5285,
+ Zo_GPMoveDown = 5297,
+ Zo_GPMoveDownOnOff = 5308,
+ Zo_GPMoveHueDown = 5324,
+ Zo_GPMoveHueUp = 5338,
+ Zo_GPMoveSatDown = 5350,
+ Zo_GPMoveSatUp = 5364,
+ Zo_GPMoveUp = 5376,
+ Zo_GPMoveUpOnOff = 5385,
+ Zo_GPOff = 5399,
+ Zo_GPOn = 5405,
+ Zo_GPPress1of1 = 5410,
+ Zo_GPPress1of2 = 5422,
+ Zo_GPPress2of2 = 5434,
+ Zo_GPRelease = 5446,
+ Zo_GPRelease1of1 = 5456,
+ Zo_GPRelease1of2 = 5470,
+ Zo_GPRelease2of2 = 5484,
+ Zo_GPSatStop = 5498,
+ Zo_GPScene0 = 5508,
+ Zo_GPScene1 = 5517,
+ Zo_GPScene10 = 5526,
+ Zo_GPScene11 = 5536,
+ Zo_GPScene12 = 5546,
+ Zo_GPScene13 = 5556,
+ Zo_GPScene14 = 5566,
+ Zo_GPScene15 = 5576,
+ Zo_GPScene2 = 5586,
+ Zo_GPScene3 = 5595,
+ Zo_GPScene4 = 5604,
+ Zo_GPScene5 = 5613,
+ Zo_GPScene6 = 5622,
+ Zo_GPScene7 = 5631,
+ Zo_GPScene8 = 5640,
+ Zo_GPScene9 = 5649,
+ Zo_GPShortPress1of1 = 5658,
+ Zo_GPShortPress1of2 = 5675,
+ Zo_GPShortPress2of2 = 5692,
+ Zo_GPStepColor = 5709,
+ Zo_GPStepDown = 5721,
+ Zo_GPStepDownOnOff = 5732,
+ Zo_GPStepHueDown = 5748,
+ Zo_GPStepHueUp = 5762,
+ Zo_GPStepSatDown = 5774,
+ Zo_GPStepSatUp = 5788,
+ Zo_GPStepUp = 5800,
+ Zo_GPStepUpOnOff = 5809,
+ Zo_GPToggle = 5823,
+ Zo_GPUnlockDoor = 5832,
+ Zo_GenericDeviceClass = 5845,
+ Zo_GenericDeviceType = 5864,
+ Zo_GetAllGroups = 5882,
+ Zo_GetGroup = 5895,
+ Zo_GetSceneMembership = 5904,
+ Zo_GlassBreak = 5923,
+ Zo_GroupNameSupport = 5934,
+ Zo_HVACSystemTypeConfiguration = 5951,
+ Zo_HWVersion = 5979,
+ Zo_HarmonicCurrentMultiplier = 5989,
+ Zo_HighTempDwellTripPoint = 6015,
+ Zo_HighTempThreshold = 6038,
+ Zo_Hue = 6056,
+ Zo_HueMove = 6060,
+ Zo_HueSat = 6068,
+ Zo_HueStep = 6075,
+ Zo_HueStepDown = 6083,
+ Zo_HueStepUp = 6095,
+ Zo_Humidity = 6105,
+ Zo_HumidityMaxMeasuredValue = 6114,
+ Zo_HumidityMinMeasuredValue = 6139,
+ Zo_HumidityTolerance = 6164,
+ Zo_IASCIEAddress = 6182,
+ Zo_Identify = 6196,
+ Zo_IdentifyQuery = 6205,
+ Zo_IdentifyTime = 6219,
+ Zo_Illuminance = 6232,
+ Zo_IlluminanceLevelStatus = 6244,
+ Zo_IlluminanceLightSensorType = 6267,
+ Zo_IlluminanceMaxMeasuredValue = 6294,
+ Zo_IlluminanceMinMeasuredValue = 6322,
+ Zo_IlluminanceTargetLevel = 6350,
+ Zo_IlluminanceTolerance = 6373,
+ Zo_InstalledClosedLimitLift = 6394,
+ Zo_InstalledClosedLimitTilt = 6419,
+ Zo_InstalledOpenLimitLift = 6444,
+ Zo_InstalledOpenLimitTilt = 6467,
+ Zo_IntermediateSetpointsLift = 6490,
+ Zo_IntermediateSetpointsTilt = 6516,
+ Zo_IntrinsicBallastFactor = 6542,
+ Zo_LampAlarmMode = 6565,
+ Zo_LampBurnHours = 6579,
+ Zo_LampBurnHoursTripPoint = 6593,
+ Zo_LampManufacturer = 6616,
+ Zo_LampRatedHours = 6633,
+ Zo_LampType = 6648,
+ Zo_LastConfiguredBy = 6657,
+ Zo_LastMessageLQI = 6674,
+ Zo_LastMessageRSSI = 6689,
+ Zo_LastSetTime = 6705,
+ Zo_LegrandHeatingMode = 6717,
+ Zo_LegrandMode = 6736,
+ Zo_LegrandOpt1 = 6748,
+ Zo_LegrandOpt2 = 6760,
+ Zo_LegrandOpt3 = 6772,
+ Zo_LidlPower = 6784,
+ Zo_LineCurrent = 6794,
+ Zo_LineCurrentPhB = 6806,
+ Zo_LineCurrentPhC = 6821,
+ Zo_LinkKey = 6836,
+ Zo_LocalTemperature = 6844,
+ Zo_LocalTemperatureCalibration = 6861,
+ Zo_LocalTime = 6889,
+ Zo_LocationAge = 6899,
+ Zo_LocationDescription = 6911,
+ Zo_LocationMethod = 6931,
+ Zo_LocationPower = 6946,
+ Zo_LocationType = 6960,
+ Zo_LockAlarmMask = 6973,
+ Zo_LockDefaultConfigurationRegister = 6987,
+ Zo_LockEnableInsideStatusLED = 7020,
+ Zo_LockEnableLocalProgramming = 7046,
+ Zo_LockEnableLogging = 7073,
+ Zo_LockEnableOneTouchLocking = 7091,
+ Zo_LockEnablePrivacyModeButton = 7117,
+ Zo_LockKeypadOperationEventMask = 7145,
+ Zo_LockKeypadProgrammingEventMask = 7174,
+ Zo_LockLEDSettings = 7205,
+ Zo_LockLanguage = 7221,
+ Zo_LockManualOperationEventMask = 7234,
+ Zo_LockOperatingMode = 7263,
+ Zo_LockRFIDOperationEventMask = 7281,
+ Zo_LockRFIDProgrammingEventMask = 7308,
+ Zo_LockRFOperationEventMask = 7337,
+ Zo_LockRFProgrammingEventMask = 7362,
+ Zo_LockSoundVolume = 7389,
+ Zo_LockState = 7405,
+ Zo_LockSupportedOperatingModes = 7415,
+ Zo_LockType = 7443,
+ Zo_LongPollInterval = 7452,
+ Zo_LongPollIntervalMin = 7469,
+ Zo_LowTempDwellTripPoint = 7489,
+ Zo_LowTempThreshold = 7511,
+ Zo_MainsAlarmMask = 7528,
+ Zo_MainsFrequency = 7543,
+ Zo_MainsVoltage = 7558,
+ Zo_MainsVoltageDwellTripPoint = 7571,
+ Zo_MainsVoltageMaxThreshold = 7598,
+ Zo_MainsVoltageMinThreshold = 7623,
+ Zo_Manufacturer = 7648,
+ Zo_MaxCoolSetpointLimit = 7661,
+ Zo_MaxHeatSetpointLimit = 7682,
+ Zo_MaxPINCodeLength = 7703,
+ Zo_MaxProxyTableEntries = 7720,
+ Zo_MaxRFIDCodeLength = 7741,
+ Zo_MaxSearchCounter = 7759,
+ Zo_MaxSinkTableEntries = 7776,
+ Zo_MaxTempExperienced = 7796,
+ Zo_Measured11thHarmonicCurrent = 7815,
+ Zo_Measured1stHarmonicCurrent = 7843,
+ Zo_Measured3rdHarmonicCurrent = 7870,
+ Zo_Measured5thHarmonicCurrent = 7897,
+ Zo_Measured7thHarmonicCurrent = 7924,
+ Zo_Measured9thHarmonicCurrent = 7951,
+ Zo_MeasuredPhase11thHarmonicCurrent = 7978,
+ Zo_MeasuredPhase1stHarmonicCurrent = 8011,
+ Zo_MeasuredPhase3rdHarmonicCurrent = 8043,
+ Zo_MeasuredPhase5thHarmonicCurrent = 8075,
+ Zo_MeasuredPhase7thHarmonicCurrent = 8107,
+ Zo_MeasuredPhase9thHarmonicCurrent = 8139,
+ Zo_MeterTypeID = 8171,
+ Zo_MinCoolSetpointLimit = 8183,
+ Zo_MinHeatSetpointLimit = 8204,
+ Zo_MinPINCodeLength = 8225,
+ Zo_MinRFIDCodeLength = 8242,
+ Zo_MinSetpointDeadBand = 8260,
+ Zo_MinTempExperienced = 8280,
+ Zo_Mode = 8299,
+ Zo_Model = 8304,
+ Zo_ModelId = 8310,
+ Zo_MotorStepSize = 8318,
+ Zo_Movement = 8332,
+ Zo_MullerLightMode = 8341,
+ Zo_MultiApplicationType = 8357,
+ Zo_MultiDescription = 8378,
+ Zo_MultiInApplicationType = 8395,
+ Zo_MultiInDescription = 8418,
+ Zo_MultiInNumberOfStates = 8437,
+ Zo_MultiInOutOfService = 8459,
+ Zo_MultiInReliability = 8479,
+ Zo_MultiInStatusFlags = 8498,
+ Zo_MultiInValue = 8517,
+ Zo_MultiNumberOfStates = 8530,
+ Zo_MultiOutApplicationType = 8550,
+ Zo_MultiOutDescription = 8574,
+ Zo_MultiOutNumberOfStates = 8594,
+ Zo_MultiOutOfService = 8617,
+ Zo_MultiOutOutOfService = 8635,
+ Zo_MultiOutReliability = 8656,
+ Zo_MultiOutRelinquishDefault = 8676,
+ Zo_MultiOutStatusFlags = 8702,
+ Zo_MultiOutValue = 8722,
+ Zo_MultiReliability = 8736,
+ Zo_MultiRelinquishDefault = 8753,
+ Zo_MultiStatusFlags = 8776,
+ Zo_MultiValue = 8793,
+ Zo_MultipleScheduling = 8804,
+ Zo_NeutralCurrent = 8823,
+ Zo_NotificationRetryNumber = 8838,
+ Zo_NotificationRetryTimer = 8862,
+ Zo_NumberOfDevices = 8885,
+ Zo_NumberOfHolidaySchedulesSupported = 8901,
+ Zo_NumberOfLogRecordsSupported = 8935,
+ Zo_NumberOfPINUsersSupported = 8963,
+ Zo_NumberOfPrimaries = 8989,
+ Zo_NumberOfRFIDUsersSupported = 9007,
+ Zo_NumberOfResets = 9034,
+ Zo_NumberOfTotalUsersSupported = 9049,
+ Zo_NumberOfWeekDaySchedulesSupportedPerUser = 9077,
+ Zo_NumberOfYearDaySchedulesSupportedPerUser = 9118,
+ Zo_NumberOfZoneSensitivityLevelsSupported = 9159,
+ Zo_NumberRSSIMeasurements = 9198,
+ Zo_NumberofActuationsLift = 9221,
+ Zo_NumberofActuationsTilt = 9244,
+ Zo_Occupancy = 9267,
+ Zo_OccupancySensorType = 9277,
+ Zo_OccupiedCoolingSetpoint = 9297,
+ Zo_OccupiedHeatingSetpoint = 9321,
+ Zo_OffTransitionTime = 9345,
+ Zo_OffWaitTime = 9363,
+ Zo_OnLevel = 9375,
+ Zo_OnOff = 9383,
+ Zo_OnOffTransitionTime = 9389,
+ Zo_OnTime = 9409,
+ Zo_OnTransitionTime = 9416,
+ Zo_OpenPeriod = 9433,
+ Zo_OppleMode = 9444,
+ Zo_OutdoorTemperature = 9454,
+ Zo_OverTempTotalDwell = 9473,
+ Zo_PICoolingDemand = 9492,
+ Zo_PIHeatingDemand = 9508,
+ Zo_PIROccupiedToUnoccupiedDelay = 9524,
+ Zo_PIRUnoccupiedToOccupiedDelay = 9553,
+ Zo_PIRUnoccupiedToOccupiedThreshold = 9582,
+ Zo_POD = 9615,
+ Zo_Panic = 9619,
+ Zo_PartNumber = 9625,
+ Zo_PathLossExponent = 9636,
+ Zo_PersistentMemoryWrites = 9653,
+ Zo_PersonalAlarm = 9676,
+ Zo_PhaseHarmonicCurrentMultiplier = 9690,
+ Zo_PhysicalClosedLimit = 9721,
+ Zo_PhysicalClosedLimitLift = 9741,
+ Zo_PhysicalClosedLimitTilt = 9765,
+ Zo_PhysicalEnvironment = 9789,
+ Zo_Power = 9809,
+ Zo_PowerDivisor = 9815,
+ Zo_PowerFactor = 9828,
+ Zo_PowerFactorPhB = 9840,
+ Zo_PowerFactorPhC = 9855,
+ Zo_PowerMultiplier = 9870,
+ Zo_PowerOffEffect = 9886,
+ Zo_PowerOnRecall = 9901,
+ Zo_PowerOnTimer = 9915,
+ Zo_PowerSource = 9928,
+ Zo_PowerThreshold = 9940,
+ Zo_Pressure = 9955,
+ Zo_PressureMaxMeasuredValue = 9964,
+ Zo_PressureMaxScaledValue = 9989,
+ Zo_PressureMinMeasuredValue = 10012,
+ Zo_PressureMinScaledValue = 10037,
+ Zo_PressureScale = 10060,
+ Zo_PressureScaledTolerance = 10074,
+ Zo_PressureScaledValue = 10098,
+ Zo_PressureTolerance = 10118,
+ Zo_Primary1Intensity = 10136,
+ Zo_Primary1X = 10154,
+ Zo_Primary1Y = 10164,
+ Zo_Primary2Intensity = 10174,
+ Zo_Primary2X = 10192,
+ Zo_Primary2Y = 10202,
+ Zo_Primary3Intensity = 10212,
+ Zo_Primary3X = 10230,
+ Zo_Primary3Y = 10240,
+ Zo_Primary4Intensity = 10250,
+ Zo_Primary4X = 10268,
+ Zo_Primary4Y = 10278,
+ Zo_Primary5Intensity = 10288,
+ Zo_Primary5X = 10306,
+ Zo_Primary5Y = 10316,
+ Zo_Primary6Intensity = 10326,
+ Zo_Primary6X = 10344,
+ Zo_Primary6Y = 10354,
+ Zo_ProductCode = 10364,
+ Zo_ProductRevision = 10376,
+ Zo_ProductURL = 10392,
+ Zo_ProxyTable = 10403,
+ Zo_QualityMeasure = 10414,
+ Zo_RGB = 10429,
+ Zo_RHDehumidificationSetpoint = 10433,
+ Zo_RMSCurrent = 10460,
+ Zo_RMSCurrentMax = 10471,
+ Zo_RMSCurrentMaxPhB = 10485,
+ Zo_RMSCurrentMaxPhC = 10502,
+ Zo_RMSCurrentMin = 10519,
+ Zo_RMSCurrentMinPhB = 10533,
+ Zo_RMSCurrentMinPhC = 10550,
+ Zo_RMSCurrentPhB = 10567,
+ Zo_RMSCurrentPhC = 10581,
+ Zo_RMSExtremeOverVoltage = 10595,
+ Zo_RMSExtremeOverVoltagePeriod = 10617,
+ Zo_RMSExtremeOverVoltagePeriodPhB = 10645,
+ Zo_RMSExtremeOverVoltagePeriodPhC = 10676,
+ Zo_RMSExtremeUnderVoltage = 10707,
+ Zo_RMSExtremeUnderVoltagePeriod = 10730,
+ Zo_RMSExtremeUnderVoltagePeriodPhB = 10759,
+ Zo_RMSExtremeUnderVoltagePeriodPhC = 10791,
+ Zo_RMSVoltage = 10823,
+ Zo_RMSVoltageMax = 10834,
+ Zo_RMSVoltageMaxPhB = 10848,
+ Zo_RMSVoltageMaxPhC = 10865,
+ Zo_RMSVoltageMin = 10882,
+ Zo_RMSVoltageMinPhB = 10896,
+ Zo_RMSVoltageMinPhC = 10913,
+ Zo_RMSVoltagePhB = 10930,
+ Zo_RMSVoltagePhC = 10944,
+ Zo_RMSVoltageSag = 10958,
+ Zo_RMSVoltageSagPeriod = 10972,
+ Zo_RMSVoltageSagPeriodPhB = 10992,
+ Zo_RMSVoltageSagPeriodPhC = 11015,
+ Zo_RMSVoltageSwell = 11038,
+ Zo_RMSVoltageSwellPeriod = 11054,
+ Zo_RMSVoltageSwellPeriodPhB = 11076,
+ Zo_RMSVoltageSwellPeriodPhC = 11101,
+ Zo_ReactiveCurrent = 11126,
+ Zo_ReactiveCurrentPhB = 11142,
+ Zo_ReactiveCurrentPhC = 11161,
+ Zo_ReactivePower = 11180,
+ Zo_ReactivePowerPhB = 11194,
+ Zo_ReactivePowerPhC = 11211,
+ Zo_RecallScene = 11228,
+ Zo_RelativeHumidity = 11240,
+ Zo_RelativeHumidityDisplay = 11257,
+ Zo_RelativeHumidityMode = 11281,
+ Zo_RemainingTime = 11302,
+ Zo_RemoteSensing = 11316,
+ Zo_RemoveAllGroups = 11330,
+ Zo_RemoveAllScenes = 11346,
+ Zo_RemoveGroup = 11362,
+ Zo_RemoveScene = 11374,
+ Zo_ReportingPeriod = 11386,
+ Zo_ResetAlarm = 11402,
+ Zo_ResetAllAlarms = 11413,
+ Zo_SWBuildID = 11428,
+ Zo_Sat = 11438,
+ Zo_SatMove = 11442,
+ Zo_SatStep = 11450,
+ Zo_SceneCount = 11458,
+ Zo_SceneNameSupport = 11469,
+ Zo_SceneValid = 11486,
+ Zo_ScheduleMode = 11497,
+ Zo_SeaPressure = 11510,
+ Zo_SecurityLevel = 11522,
+ Zo_ServerActiveFunctionality = 11536,
+ Zo_ServerFunctionality = 11562,
+ Zo_SharedSecurityKey = 11582,
+ Zo_SharedSecurityKeyType = 11600,
+ Zo_ShortPollInterval = 11622,
+ Zo_Shutter = 11640,
+ Zo_ShutterClose = 11648,
+ Zo_ShutterLift = 11661,
+ Zo_ShutterOpen = 11673,
+ Zo_ShutterStop = 11685,
+ Zo_ShutterTilt = 11697,
+ Zo_SinkTable = 11709,
+ Zo_SoftwareRevision = 11719,
+ Zo_StackVersion = 11736,
+ Zo_StandardTime = 11749,
+ Zo_StartUpOnOff = 11762,
+ Zo_Status = 11775,
+ Zo_StoreScene = 11782,
+ Zo_SwitchActions = 11793,
+ Zo_SwitchType = 11807,
+ Zo_SystemMode = 11818,
+ Zo_TRVBoost = 11829,
+ Zo_TRVChildProtection = 11838,
+ Zo_TRVMirrorDisplay = 11857,
+ Zo_TRVMode = 11874,
+ Zo_TRVWindowOpen = 11882,
+ Zo_TempTarget = 11896,
+ Zo_Temperature = 11907,
+ Zo_TemperatureDisplayMode = 11919,
+ Zo_TemperatureMaxMeasuredValue = 11942,
+ Zo_TemperatureMinMeasuredValue = 11970,
+ Zo_TemperatureTolerance = 11998,
+ Zo_TerncyDuration = 12019,
+ Zo_TerncyRotate = 12034,
+ Zo_ThSetpoint = 12047,
+ Zo_ThermostatAlarmMask = 12058,
+ Zo_ThermostatKeypadLockout = 12078,
+ Zo_ThermostatOccupancy = 12102,
+ Zo_ThermostatRunningMode = 12122,
+ Zo_ThermostatScheduleProgrammingVisibility = 12144,
+ Zo_Time = 12184,
+ Zo_TimeEpoch = 12189,
+ Zo_TimeStatus = 12199,
+ Zo_TimeZone = 12210,
+ Zo_TotalActivePower = 12219,
+ Zo_TotalApparentPower = 12236,
+ Zo_TotalProfileNum = 12255,
+ Zo_TotalReactivePower = 12271,
+ Zo_TuyaCalibration = 12290,
+ Zo_TuyaCalibrationTime = 12306,
+ Zo_TuyaMCUVersion = 12326,
+ Zo_TuyaMotorReversal = 12341,
+ Zo_TuyaMovingState = 12359,
+ Zo_TuyaQuery = 12375,
+ Zo_UnoccupiedCoolingSetpoint = 12385,
+ Zo_UnoccupiedHeatingSetpoint = 12411,
+ Zo_UtilityName = 12437,
+ Zo_ValidUntilTime = 12449,
+ Zo_ValvePosition = 12464,
+ Zo_VelocityLift = 12478,
+ Zo_ViewGroup = 12491,
+ Zo_ViewScene = 12501,
+ Zo_Water = 12511,
+ Zo_WhitePointX = 12517,
+ Zo_WhitePointY = 12529,
+ Zo_WindowCoveringType = 12541,
+ Zo_X = 12560,
+ Zo_Y = 12562,
+ Zo_ZCLVersion = 12564,
+ Zo_ZoneID = 12575,
+ Zo_ZoneState = 12582,
+ Zo_ZoneStatus = 12592,
+ Zo_ZoneStatusChange = 12603,
+ Zo_ZoneType = 12620,
+ Zo__ = 12629,
+ Zo_xx = 12631,
+ Zo_xx000A00 = 12634,
+ Zo_xx0A = 12643,
+ Zo_xx0A00 = 12648,
+ Zo_xx19 = 12655,
+ Zo_xx190A = 12660,
+ Zo_xx190A00 = 12667,
+ Zo_xxxx = 12676,
+ Zo_xxxx00 = 12681,
+ Zo_xxxx0A00 = 12688,
+ Zo_xxxxyy = 12697,
+ Zo_xxxxyyyy = 12704,
+ Zo_xxxxyyyy0A00 = 12713,
+ Zo_xxxxyyzz = 12726,
+ Zo_xxyy = 12735,
+ Zo_xxyy0A00 = 12740,
+ Zo_xxyyyy = 12749,
+ Zo_xxyyyy000000000000 = 12756,
+ Zo_xxyyyy0A0000000000 = 12775,
+ Zo_xxyyyyzz = 12794,
+ Zo_xxyyyyzzzz = 12803,
+ Zo_xxyyzzzz = 12814,
+};
+
+
+/*
+ DO NOT EDIT
+*/
+
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino
new file mode 100644
index 000000000..17be8d13f
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino
@@ -0,0 +1,1336 @@
+/*
+ xdrv_23_zigbee_converters.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+/*********************************************************************************************\
+ * ZCL
+\*********************************************************************************************/
+
+
+enum Z_DataTypes {
+ Znodata = 0x00,
+ Zdata8 = 0x08, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
+ Zbool = 0x10,
+ Zmap8 = 0x18, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
+ Zuint8 = 0x20, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
+ Zint8 = 0x28, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
+ Zenum8 = 0x30, Zenum16 = 0x31,
+ Zsemi = 0x38, Zsingle = 0x39, Zdouble = 0x3A,
+ Zoctstr = 0x41, Zstring = 0x42, Zoctstr16 = 0x43, Zstring16 = 0x44,
+ Zarrray = 0x48,
+ Zstruct = 0x4C,
+ Zset = 0x50, Zbag = 0x51,
+ ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
+ ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
+ ZEUI64 = 0xF0, Zkey128 = 0xF1,
+ Zunk = 0xFF,
+ // adding fake type for Tuya specific encodings
+ Ztuya0 = Zoctstr,
+ Ztuya1 = Zbool,
+ Ztuya2 = Zint32,
+ Ztuya3 = Zstring,
+ Ztuya4 = Zuint8,
+ Ztuya5 = Zuint32
+};
+
+const char Z_DATATYPES[] PROGMEM =
+ "nodata|"
+ "data8|data16|data24|data32|data40|data48|data56|data64|"
+ "bool|"
+ "map8|map16|map24|map32|map40|map48|map56|map64|"
+ "uint8|uint16|uint24|uint32|uint40|uint48|uint56|uint64|"
+ "int8|int16|int24|int32|int40|int48|int56|int64|"
+ "enum8|enum16|"
+ "semi|single|double|"
+ "octstr|string|octstr16|string16|"
+ "array|struct|set|bag|"
+ "ToD|date|UTC|"
+ "clusterId|attribId|bacOID|"
+ "EUI64|key128|"
+ "unk"
+;
+
+const uint8_t Z_DATA_TYPES_CODE[] PROGMEM = {
+ Znodata,
+ Zdata8, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
+ Zbool,
+ Zmap8, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
+ Zuint8, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
+ Zint8, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
+ Zenum8, Zenum16,
+ Zsemi, Zsingle, Zdouble,
+ Zoctstr, Zstring, Zoctstr16, Zstring16,
+ Zarrray, Zstruct, Zset, Zbag,
+ ZToD, Zdate, ZUTC,
+ ZclusterId, ZattribId, ZbacOID,
+ ZEUI64, Zkey128,
+ Zunk,
+};
+
+// convert a type into a name, or HEX if none found
+void Z_getTypeByNumber(char *destination, size_t destination_size, uint8_t type) {
+ for (uint32_t i = 0; i < ARRAY_SIZE(Z_DATA_TYPES_CODE); i++) {
+ if (type == pgm_read_byte(&Z_DATA_TYPES_CODE[i])) {
+ GetTextIndexed(destination, destination_size, i, Z_DATATYPES);
+ return;
+ }
+ }
+ snprintf(destination, destination_size, "%02X", type);
+}
+
+// convert a string to a type, or Zunk if no match
+uint8_t Z_getTypeByName(const char *type) {
+ if (type == nullptr) { return Zunk; }
+ char type_found[16];
+ int32_t ret = GetCommandCode(type_found, sizeof(type_found), type, Z_DATATYPES);
+ if (ret < 0) {
+ // try to decode hex
+ size_t type_len = strlen_P(type);
+ if (type_len > 0 && type_len <= 2) {
+ char *type_end;
+ ret = strtoul(type, &type_end, 16);
+ if (type_end == type) { ret = Zunk; } // could not decode
+ }
+ return ret;
+ } else {
+ return pgm_read_byte(&Z_DATA_TYPES_CODE[ret]);
+ }
+}
+//
+// get the lenth in bytes for a data-type
+// return 0 if unknown of type specific
+//
+// Note: this code is smaller than a static array
+uint8_t Z_getDatatypeLen(uint8_t t) {
+ if ( ((t >= 0x08) && (t <= 0x0F)) || // data8 - data64
+ ((t >= 0x18) && (t <= 0x2F)) ) { // map/uint/int
+ return (t & 0x07) + 1;
+ }
+ switch (t) {
+ case Zbool:
+ case Zenum8:
+ return 1;
+ case Zenum16:
+ case Zsemi:
+ case ZclusterId:
+ case ZattribId:
+ return 2;
+ case Zsingle:
+ case ZToD:
+ case Zdate:
+ case ZUTC:
+ case ZbacOID:
+ return 4;
+ case Zdouble:
+ case ZEUI64:
+ return 8;
+ case Zkey128:
+ return 16;
+ case Znodata:
+ default:
+ return 0;
+ }
+}
+
+// is the type a discrete type, cf. section 2.6.2 of ZCL spec
+bool Z_isDiscreteDataType(uint8_t t) {
+ if ( ((t >= 0x20) && (t <= 0x2F)) || // uint8 - int64
+ ((t >= 0x38) && (t <= 0x3A)) || // semi - double
+ ((t >= 0xE0) && (t <= 0xE2)) ) { // ToD - UTC
+ return false;
+ } else {
+ return true;
+ }
+}
+
+typedef struct Z_AttributeConverter {
+ uint8_t type;
+ uint8_t cluster_short;
+ uint16_t attribute;
+ uint16_t name_offset;
+ uint8_t multiplier_idx; // multiplier index for numerical value, use CmToMultiplier(), (if > 0 multiply by x, if <0 device by x)
+ // the high 4 bits are used to encode flags
+ // currently: 0x80 = this parameter needs to be exported to ZbData
+ uint8_t mapping; // high 4 bits = type, low 4 bits = offset in bytes from header
+ // still room for a byte
+} Z_AttributeConverter;
+
+// Get offset in bytes of attributes, starting after the header (skipping first 4 bytes)
+#define Z_OFFSET(c,a) (offsetof(class c, a) - sizeof(Z_Data))
+#define Z_CLASS(c) c // necessary to get a valid token without concatenation (which wouldn't work)
+#define Z_MAPPING(c,a) (((((uint8_t)Z_CLASS(c)::type) & 0x0F) << 4) | Z_OFFSET(c,a))
+
+// lines with this marker, will be used to export automatically data to `ZbData`
+// at the condition Z_MAPPING() is also used
+const uint8_t Z_EXPORT_DATA = 0x80;
+
+// Cluster numbers are store in 8 bits format to save space,
+// the following tables allows the conversion from 8 bits index Cx...
+// to the 16 bits actual cluster number
+enum Cx_cluster_short {
+ Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007,
+ Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,
+ Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0021,
+ Cx0100, Cx0101, Cx0102, Cx0201, Cx0202, Cx0203, Cx0204,
+ Cx0300, Cx0301, Cx0400, Cx0401, Cx0402, Cx0403,
+ Cx0404, Cx0405, Cx0406, Cx0500, Cx0702, Cx0B01, Cx0B04, Cx0B05,
+ CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
+};
+
+const uint16_t Cx_cluster[] PROGMEM = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0021,
+ 0x0100, 0x0101, 0x0102, 0x0201, 0x0202, 0x0203, 0x0204,
+ 0x0300, 0x0301, 0x0400, 0x0401, 0x0402, 0x0403,
+ 0x0404, 0x0405, 0x0406, 0x0500, 0x0702, 0x0B01, 0x0B04, 0x0B05,
+ 0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
+};
+
+uint16_t CxToCluster(uint8_t cx) {
+ if (cx < nitems(Cx_cluster)) {
+ return pgm_read_word(&Cx_cluster[cx]);
+ }
+ return 0xFFFF;
+}
+
+uint8_t ClusterToCx(uint16_t cluster) {
+ for (uint32_t i=0; i 31) { str_len = 31; }
+ attr = (char*) malloc(str_len + 1);
+ strlcpy(attr, str, str_len + 1);
+ }
+}
+
+//
+//
+// Class for a single attribute from a plugin
+//
+//
+class Z_plugin_attribute {
+public:
+
+ Z_plugin_attribute(void) :
+ type(Zunk),
+ multiplier(1), divider(1), base(0),
+ cluster(0xFFFF), attribute(0xFFFF), manuf(0),
+ name(nullptr)
+ {};
+
+ ~Z_plugin_attribute(void) {
+ if (name != nullptr) { free((void*)name); }
+ }
+
+ inline void setName(const char *_name) {
+ Z_setString(this->name, _name);
+ }
+
+ void set(uint16_t cluster, uint16_t attribute, const char *_name, uint8_t type = Zunk) {
+ this->cluster = cluster;
+ this->attribute = attribute;
+ Z_setString(this->name, _name);
+ this->name = name;
+ this->type = type;
+ }
+
+ uint8_t type; // zigbee type, Zunk by default
+ int8_t multiplier; // multiply by x (ignore if 0 or 1)
+ int8_t divider; // divide by x (ignore if 0 or 1)
+ int16_t base; // add x (ignore if 0)
+ uint16_t cluster; // cluster number
+ uint16_t attribute; // attribute number
+ uint16_t manuf; // manufacturer code, 0 if none
+ char * name; // name of attribute once converted
+};
+
+//
+// Class for an attribute synonym
+//
+class Z_attribute_synonym {
+public:
+ Z_attribute_synonym(void) :
+ cluster(0xFFFF), attribute(0xFFFF), new_cluster(0xFFFF), new_attribute(0xFFFF),
+ multiplier(1), divider(1), base(0)
+ {};
+
+ void set(uint16_t cluster, uint16_t attribute, uint16_t new_cluster, uint16_t new_attribute,
+ int8_t multiplier = 1, int8_t divider = 1, int16_t base = 0) {
+ this->cluster = cluster;
+ this->attribute = attribute;
+ this->new_cluster = new_cluster;
+ this->new_attribute = new_attribute;
+ this->multiplier = multiplier;
+ this->divider = divider;
+ this->base = base;
+ }
+
+ inline bool found(void) const { return cluster != 0xFFFF && attribute != 0xFFFF; }
+
+ uint16_t cluster; // cluster to match
+ uint16_t attribute; // attribute to match
+ uint16_t new_cluster; // replace with this cluster
+ uint16_t new_attribute; // replace with this attribute
+ int8_t multiplier; // multiply by x (ignore if 0 or 1)
+ int8_t divider; // divide by x (ignore if 0 or 1)
+ int16_t base; // add x (ignore if 0)
+};
+
+//
+//
+// matcher for a device, based on ModelID and Manufacturer
+//
+//
+class Z_plugin_matcher {
+public:
+
+ Z_plugin_matcher(void) {};
+
+ inline void setModel(const char *_model) {
+ Z_setString(this->model, _model);
+ }
+
+
+ inline void setManuf(const char *_manuf) {
+ Z_setString(this->manufacturer, _manuf);
+ }
+
+ ~Z_plugin_matcher(void) {
+ if (model) { free((void*)model); }
+ if (manufacturer) { free((void*)manufacturer); }
+ }
+
+ // check if a matches b, return true if so
+ //
+ // Special behavior:
+ // - return true if `a` is empty or null
+ // - return false if `b` is null
+ // - matches start of `a` if `a` ends with `'*'`
+ // - exact match otherwise
+ static bool matchStar(const char *_a, const char *_b) {
+ if (_a == nullptr || *_a == '\0') { return true; }
+ if (_b == nullptr) { return false; }
+
+ const char *a = _a;
+ const char *b = _b;
+ while (1) {
+ if (a[0] == '*' && a[1] == '\0') { // pattern ends with '*'
+ return true; // matches worked until now, accept match
+ }
+ if (*a != *b) {
+ return false;
+ }
+ if (*a == '\0') {
+ return true;
+ }
+ a++;
+ b++;
+ }
+ }
+
+ bool match(const char *match_model, const char *match_manuf) const {
+ bool match = true;
+ if (!matchStar(model, match_model)) {
+ match = false;
+ }
+ if (!matchStar(manufacturer, match_manuf)) {
+ match = false;
+ }
+ // AddLog(LOG_LEVEL_DEBUG, ">match device(%s, %s) compared to (%s, %s) = %i", match_model, match_manuf, model ? model : "", manufacturer ? manufacturer : "", match);
+ return match;
+ }
+
+ char * model = nullptr;
+ char * manufacturer = nullptr;
+};
+
+//
+//
+// Z_plugin_template : template for a group of devices
+//
+//
+class Z_plugin_template {
+public:
+
+ LList matchers;
+ LList synonyms;
+ LList attributes;
+ char * filename = nullptr; // the filename from which it was imported
+ // needs to be freed by ZbUnload only (to allow reuse between mutliple instances)
+};
+
+//
+//
+// Z_plugin_templates
+//
+//
+class Z_plugin_templates : public LList {
+public:
+ // match an attribute from the plug-in in-memory database
+ // returns `nullptr` if none found
+ // or a pointer to `Z_plugin_attribute`
+ const Z_plugin_attribute * matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+ const Z_plugin_attribute * matchAttributeByName(const char *model, const char *manufacturer, const char *name);
+ const Z_attribute_synonym * matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+};
+
+const Z_attribute_synonym * Z_plugin_templates::matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & synonyms = tmpl.synonyms; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_attribute_synonym & syn : synonyms) {
+ if (syn.cluster == cluster && syn.attribute == attribute) {
+ return &syn;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+const Z_plugin_attribute * Z_plugin_templates::matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & attributes = tmpl.attributes; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_plugin_attribute & attr : attributes) {
+ if (attr.cluster == cluster && attr.attribute == attribute) {
+ return &attr;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+const Z_plugin_attribute * Z_plugin_templates::matchAttributeByName(const char *model, const char *manufacturer, const char * name) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & attributes = tmpl.attributes; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_plugin_attribute & attr : attributes) {
+ if (0 == strcasecmp_P(name, attr.name ? attr.name : "")) {
+ return &attr;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+//
+// Attribute match result
+//
+class Z_attribute_match {
+public:
+ inline bool found(void) const { return (cluster != 0xFFFF && attribute != 0xFFFF); }
+
+ uint16_t cluster = 0xFFFF;
+ uint16_t attribute = 0xFFFF;
+ const char * name = nullptr;
+ uint8_t zigbee_type = Znodata;
+ int8_t multiplier = 1;
+ int8_t divider = 1;
+ int8_t base = 0;
+ uint8_t map_offset = 0;
+ Z_Data_Type map_type = Z_Data_Type::Z_Unknown;
+ uint16_t manuf = 0x0000; // manuf code (if any)
+};
+
+Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+Z_attribute_match Z_plugin_matchAttributeByName(const char *model, const char *manufacturer, const char *name);
+
+
+// list of post-processing directives
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof" // avoid warnings since we're using offsetof() in a risky way
+const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
+ { Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), Cm1, 0 },
+ { Zuint8, Cx0000, 0x0001, Z_(AppVersion), Cm1, 0 },
+ { Zuint8, Cx0000, 0x0002, Z_(StackVersion), Cm1, 0 },
+ { Zuint8, Cx0000, 0x0003, Z_(HWVersion), Cm1, 0 },
+ { Zstring, Cx0000, 0x0004, Z_(Manufacturer), Cm1, 0 }, // record Manufacturer
+ { Zstring, Cx0000, 0x0005, Z_(ModelId), Cm1, 0 }, // record Model
+ { Zstring, Cx0000, 0x0006, Z_(DateCode), Cm1, 0 },
+ { Zenum8, Cx0000, 0x0007, Z_(PowerSource), Cm1, 0 },
+ { Zenum8, Cx0000, 0x0008, Z_(GenericDeviceClass), Cm1, 0 },
+ { Zenum8, Cx0000, 0x0009, Z_(GenericDeviceType), Cm1, 0 },
+ { Zoctstr, Cx0000, 0x000A, Z_(ProductCode), Cm1, 0 },
+ { Zstring, Cx0000, 0x000B, Z_(ProductURL), Cm1, 0 },
+ { Zstring, Cx0000, 0x0010, Z_(LocationDescription), Cm1, 0 },
+ { Zenum8, Cx0000, 0x0011, Z_(PhysicalEnvironment), Cm1, 0 },
+ { Zbool, Cx0000, 0x0012, Z_(DeviceEnabled), Cm1, 0 },
+ { Zmap8, Cx0000, 0x0013, Z_(AlarmMask), Cm1, 0 },
+ { Zmap8, Cx0000, 0x0014, Z_(DisableLocalConfig), Cm1, 0 },
+ { Zstring, Cx0000, 0x4000, Z_(SWBuildID), Cm1, 0 },
+ { Zuint8, Cx0000, 0x4005, Z_(MullerLightMode), Cm1, 0 },
+ // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
+ { Zmap8, Cx0000, 0xFF01, Z_(), Cm0, 0 },
+ { Zmap8, Cx0000, 0xFF02, Z_(), Cm0, 0 },
+ // { Zmap8, Cx0000, 0xFF01, Z_(), Cm0, Z_AqaraSensor, 0 },
+ // { Zmap8, Cx0000, 0xFF02, Z_(), Cm0, Z_AqaraSensor2, 0 },
+
+ // Power Configuration cluster
+ { Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0001, Z_(MainsFrequency), Cm1, 0 },
+ { Zmap8, Cx0001, 0x0010, Z_(MainsAlarmMask), Cm1, 0 },
+ { Zuint16, Cx0001, 0x0011, Z_(MainsVoltageMinThreshold),Cm1, 0 },
+ { Zuint16, Cx0001, 0x0012, Z_(MainsVoltageMaxThreshold),Cm1, 0 },
+ { Zuint16, Cx0001, 0x0013, Z_(MainsVoltageDwellTripPoint),Cm1, 0 },
+ { Zuint8, Cx0001, 0x0020, Z_(BatteryVoltage), Cm_10, 0 }, // divide by 10
+ { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), Cm_2, 0 }, // divide by 2
+ { Zstring, Cx0001, 0x0030, Z_(BatteryManufacturer), Cm1, 0 },
+ { Zenum8, Cx0001, 0x0031, Z_(BatterySize), Cm1, 0 },
+ { Zuint16, Cx0001, 0x0032, Z_(BatteryAHrRating), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0033, Z_(BatteryQuantity), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0034, Z_(BatteryRatedVoltage), Cm1, 0 },
+ { Zmap8, Cx0001, 0x0035, Z_(BatteryAlarmMask), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0036, Z_(BatteryVoltageMinThreshold), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0037, Z_(BatteryVoltageThreshold1), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0038, Z_(BatteryVoltageThreshold2), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0039, Z_(BatteryVoltageThreshold3), Cm1, 0 },
+ { Zuint8, Cx0001, 0x003A, Z_(BatteryPercentageMinThreshold), Cm1, 0 },
+ { Zuint8, Cx0001, 0x003B, Z_(BatteryPercentageThreshold1), Cm1, 0 },
+ { Zuint8, Cx0001, 0x003C, Z_(BatteryPercentageThreshold2), Cm1, 0 },
+ { Zuint8, Cx0001, 0x003D, Z_(BatteryPercentageThreshold3), Cm1, 0 },
+ { Zmap32, Cx0001, 0x003E, Z_(BatteryAlarmState), Cm1, 0 },
+ // { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), Cm_2, Z_BatteryPercentage, 0 }, // divide by 2
+
+ // Device Temperature Configuration cluster
+ { Zint16, Cx0002, 0x0000, Z_(CurrentTemperature), Cm1, 0 },
+ { Zint16, Cx0002, 0x0001, Z_(MinTempExperienced), Cm1, 0 },
+ { Zint16, Cx0002, 0x0002, Z_(MaxTempExperienced), Cm1, 0 },
+ { Zuint16, Cx0002, 0x0003, Z_(OverTempTotalDwell), Cm1, 0 },
+ { Zmap8, Cx0002, 0x0010, Z_(DeviceTempAlarmMask), Cm1, 0 },
+ { Zint16, Cx0002, 0x0011, Z_(LowTempThreshold), Cm1, 0 },
+ { Zint16, Cx0002, 0x0012, Z_(HighTempThreshold), Cm1, 0 },
+ { Zuint24, Cx0002, 0x0013, Z_(LowTempDwellTripPoint), Cm1, 0 },
+ { Zuint24, Cx0002, 0x0014, Z_(HighTempDwellTripPoint), Cm1, 0 },
+
+ // Identify cluster
+ { Zuint16, Cx0003, 0x0000, Z_(IdentifyTime), Cm1, 0 },
+
+ // Groups cluster
+ { Zmap8, Cx0004, 0x0000, Z_(GroupNameSupport), Cm1, 0 },
+
+ // Scenes cluster
+ { Zuint8, Cx0005, 0x0000, Z_(SceneCount), Cm1, 0 },
+ { Zuint8, Cx0005, 0x0001, Z_(CurrentScene), Cm1, 0 },
+ { Zuint16, Cx0005, 0x0002, Z_(CurrentGroup), Cm1, 0 },
+ { Zbool, Cx0005, 0x0003, Z_(SceneValid), Cm1, 0 },
+ { Zmap8, Cx0005, 0x0004, Z_(SceneNameSupport), Cm1, 0 },
+ { ZEUI64, Cx0005, 0x0005, Z_(LastConfiguredBy), Cm1, 0 },
+ //{ Zmap8, Cx0005, 0x0004, (NameSupport), Cm1, 0 },
+
+ // On/off cluster
+ { Zbool, Cx0006, 0x0000, Z_(Power), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_OnOff, power) },
+ { Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), Cm1, 0 },
+ { Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280
+ { Zbool, Cx0006, 0x4000, Z_(OnOff), Cm1, 0 },
+ { Zuint16, Cx0006, 0x4001, Z_(OnTime), Cm1, 0 },
+ { Zuint16, Cx0006, 0x4002, Z_(OffWaitTime), Cm1, 0 },
+
+ // On/Off Switch Configuration cluster
+ { Zenum8, Cx0007, 0x0000, Z_(SwitchType), Cm1, 0 },
+ { Zenum8, Cx0007, 0x0010, Z_(SwitchActions), Cm1, 0 },
+
+ // Level Control cluster
+ { Zuint8, Cx0008, 0x0000, Z_(Dimmer), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, dimmer) },
+ { Zuint16, Cx0008, 0x0001, Z_(DimmerRemainingTime), Cm1, 0 },
+ { Zuint8, Cx0008, 0x0002, Z_(DimmerMinLevel), Cm1, 0 },
+ { Zuint8, Cx0008, 0x0003, Z_(DimmerMaxLevel), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0004, Z_(DimmerCurrentFrequency),Cm1, 0 },
+ { Zuint16, Cx0008, 0x0005, Z_(DimmerMinFrequency), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0006, Z_(DimmerMaxFrequency), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0010, Z_(OnOffTransitionTime), Cm1, 0 },
+ { Zuint8, Cx0008, 0x0011, Z_(OnLevel), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0012, Z_(OnTransitionTime), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0013, Z_(OffTransitionTime), Cm1, 0 },
+ { Zuint16, Cx0008, 0x0014, Z_(DefaultMoveRate), Cm1, 0 },
+ { Zmap8, Cx0008, 0x000F, Z_(DimmerOptions), Cm1, 0 },
+ { Zuint8, Cx0008, 0x4000, Z_(DimmerStartUpLevel), Cm1, 0 },
+
+ // Alarms cluster
+ { Zuint16, Cx0009, 0x0000, Z_(AlarmCount), Cm1, 0 },
+
+ // Time cluster
+ { ZUTC, Cx000A, 0x0000, Z_(Time), Cm1, 0 },
+ { Zmap8, Cx000A, 0x0001, Z_(TimeStatus), Cm1, 0 },
+ { Zint32, Cx000A, 0x0002, Z_(TimeZone), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0003, Z_(DstStart), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0004, Z_(DstEnd), Cm1, 0 },
+ { Zint32, Cx000A, 0x0005, Z_(DstShift), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0006, Z_(StandardTime), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0007, Z_(LocalTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0x0008, Z_(LastSetTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0x0009, Z_(ValidUntilTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0xFF00, Z_(TimeEpoch), Cm1, 0 }, // Tasmota specific, epoch
+
+ // RSSI Location cluster
+ { Zdata8, Cx000B, 0x0000, Z_(LocationType), Cm1, 0 },
+ { Zenum8, Cx000B, 0x0001, Z_(LocationMethod), Cm1, 0 },
+ { Zuint16, Cx000B, 0x0002, Z_(LocationAge), Cm1, 0 },
+ { Zuint8, Cx000B, 0x0003, Z_(QualityMeasure), Cm1, 0 },
+ { Zuint8, Cx000B, 0x0004, Z_(NumberOfDevices), Cm1, 0 },
+ { Zint16, Cx000B, 0x0010, Z_(Coordinate1), Cm1, 0 },
+ { Zint16, Cx000B, 0x0011, Z_(Coordinate2), Cm1, 0 },
+ { Zint16, Cx000B, 0x0012, Z_(Coordinate3), Cm1, 0 },
+ { Zint16, Cx000B, 0x0013, Z_(LocationPower), Cm1, 0 },
+ { Zuint16, Cx000B, 0x0014, Z_(PathLossExponent), Cm1, 0 },
+ { Zuint16, Cx000B, 0x0015, Z_(ReportingPeriod), Cm1, 0 },
+ { Zuint16, Cx000B, 0x0016, Z_(CalculationPeriod), Cm1, 0 },
+ { Zuint8, Cx000B, 0x0016, Z_(NumberRSSIMeasurements), Cm1, 0 },
+
+ // Analog Input cluster
+ // { 0xFF, Cx000C, 0x0004, (AnalogInActiveText), Cm1, 0 },
+ { Zstring, Cx000C, 0x001C, Z_(AnalogInDescription), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x002E, (AnalogInInactiveText), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0041, Z_(AnalogInMaxValue), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0045, Z_(AnalogInMinValue), Cm1, 0 },
+ { Zbool, Cx000C, 0x0051, Z_(AnalogInOutOfService), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0055, Z_(AnalogValue), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x0057, (AnalogInPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000C, 0x0067, Z_(AnalogInReliability), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x0068, (AnalogInRelinquishDefault),Cm1, 0 },
+ { Zsingle, Cx000C, 0x006A, Z_(AnalogInResolution), Cm1, 0 },
+ { Zmap8, Cx000C, 0x006F, Z_(AnalogInStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000C, 0x0075, Z_(AnalogInEngineeringUnits),Cm1, 0 },
+ { Zuint32, Cx000C, 0x0100, Z_(AnalogInApplicationType),Cm1, 0 },
+ { Zuint16, Cx000C, 0xFF55, Z_(AqaraRotate), Cm1, 0 },
+ { Zuint16, Cx000C, 0xFF05, Z_(Aqara_FF05), Cm1, 0 },
+
+ // Analog Output cluster
+ { Zstring, Cx000D, 0x001C, Z_(AnalogOutDescription), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0041, Z_(AnalogOutMaxValue), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0045, Z_(AnalogOutMinValue), Cm1, 0 },
+ { Zbool, Cx000D, 0x0051, Z_(AnalogOutOutOfService),Cm1, 0 },
+ { Zsingle, Cx000D, 0x0055, Z_(AnalogOutValue), Cm1, 0 },
+ // { Zunk, Cx000D, 0x0057, (AnalogOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000D, 0x0067, Z_(AnalogOutReliability), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0068, Z_(AnalogOutRelinquishDefault), Cm1, 0 },
+ { Zsingle, Cx000D, 0x006A, Z_(AnalogOutResolution), Cm1, 0 },
+ { Zmap8, Cx000D, 0x006F, Z_(AnalogOutStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000D, 0x0075, Z_(AnalogOutEngineeringUnits), Cm1, 0 },
+ { Zuint32, Cx000D, 0x0100, Z_(AnalogOutApplicationType), Cm1, 0 },
+
+ // Analog Value cluster
+ { Zstring, Cx000E, 0x001C, Z_(AnalogDescription), Cm1, 0 },
+ { Zbool, Cx000E, 0x0051, Z_(AnalogOutOfService), Cm1, 0 },
+ { Zsingle, Cx000E, 0x0055, Z_(AnalogValue), Cm1, 0 },
+ { Zunk, Cx000E, 0x0057, Z_(AnalogPriorityArray), Cm1, 0 },
+ { Zenum8, Cx000E, 0x0067, Z_(AnalogReliability), Cm1, 0 },
+ { Zsingle, Cx000E, 0x0068, Z_(AnalogRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx000E, 0x006F, Z_(AnalogStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000E, 0x0075, Z_(AnalogEngineeringUnits),Cm1, 0 },
+ { Zuint32, Cx000E, 0x0100, Z_(AnalogApplicationType),Cm1, 0 },
+
+ // Binary Input cluster
+ { Zstring, Cx000F, 0x0004, Z_(BinaryInActiveText), Cm1, 0 },
+ { Zstring, Cx000F, 0x001C, Z_(BinaryInDescription), Cm1, 0 },
+ { Zstring, Cx000F, 0x002E, Z_(BinaryInInactiveText),Cm1, 0 },
+ { Zbool, Cx000F, 0x0051, Z_(BinaryInOutOfService),Cm1, 0 },
+ { Zenum8, Cx000F, 0x0054, Z_(BinaryInPolarity), Cm1, 0 },
+ { Zstring, Cx000F, 0x0055, Z_(BinaryInValue), Cm1, 0 },
+ // { 0xFF, Cx000F, 0x0057, (BinaryInPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000F, 0x0067, Z_(BinaryInReliability), Cm1, 0 },
+ { Zmap8, Cx000F, 0x006F, Z_(BinaryInStatusFlags), Cm1, 0 },
+ { Zuint32, Cx000F, 0x0100, Z_(BinaryInApplicationType),Cm1, 0 },
+
+ // Binary Output cluster
+ { Zstring, Cx0010, 0x0004, Z_(BinaryOutActiveText), Cm1, 0 },
+ { Zstring, Cx0010, 0x001C, Z_(BinaryOutDescription), Cm1, 0 },
+ { Zstring, Cx0010, 0x002E, Z_(BinaryOutInactiveText),Cm1, 0 },
+ { Zuint32, Cx0010, 0x0042, Z_(BinaryOutMinimumOffTime),Cm1, 0 },
+ { Zuint32, Cx0010, 0x0043, Z_(BinaryOutMinimumOnTime),Cm1, 0 },
+ { Zbool, Cx0010, 0x0051, Z_(BinaryOutOutOfService),Cm1, 0 },
+ { Zenum8, Cx0010, 0x0054, Z_(BinaryOutPolarity), Cm1, 0 },
+ { Zbool, Cx0010, 0x0055, Z_(BinaryOutValue), Cm1, 0 },
+ // { Zunk, Cx0010, 0x0057, (BinaryOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx0010, 0x0067, Z_(BinaryOutReliability), Cm1, 0 },
+ { Zbool, Cx0010, 0x0068, Z_(BinaryOutRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0010, 0x006F, Z_(BinaryOutStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0010, 0x0100, Z_(BinaryOutApplicationType),Cm1, 0 },
+
+ // Binary Value cluster
+ { Zstring, Cx0011, 0x0004, Z_(BinaryActiveText), Cm1, 0 },
+ { Zstring, Cx0011, 0x001C, Z_(BinaryDescription), Cm1, 0 },
+ { Zstring, Cx0011, 0x002E, Z_(BinaryInactiveText), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0042, Z_(BinaryMinimumOffTime), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0043, Z_(BinaryMinimumOnTime), Cm1, 0 },
+ { Zbool, Cx0011, 0x0051, Z_(BinaryOutOfService), Cm1, 0 },
+ { Zbool, Cx0011, 0x0055, Z_(BinaryValue), Cm1, 0 },
+ // { Zunk, Cx0011, 0x0057, (BinaryPriorityArray), Cm1, 0 },
+ { Zenum8, Cx0011, 0x0067, Z_(BinaryReliability), Cm1, 0 },
+ { Zbool, Cx0011, 0x0068, Z_(BinaryRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0011, 0x006F, Z_(BinaryStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0100, Z_(BinaryApplicationType),Cm1, 0 },
+
+ // Multistate Input cluster
+ // { Zunk, Cx0012, 0x000E, (MultiInStateText), Cm1, 0 },
+ { Zstring, Cx0012, 0x001C, Z_(MultiInDescription), Cm1, 0 },
+ { Zuint16, Cx0012, 0x004A, Z_(MultiInNumberOfStates),Cm1, 0 },
+ { Zbool, Cx0012, 0x0051, Z_(MultiInOutOfService), Cm1, 0 },
+ { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm1, 0 },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm0, Z_AqaraCube, 0 },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm0, Z_AqaraButton, 0 },
+ { Zenum8, Cx0012, 0x0067, Z_(MultiInReliability), Cm1, 0 },
+ { Zmap8, Cx0012, 0x006F, Z_(MultiInStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0012, 0x0100, Z_(MultiInApplicationType),Cm1, 0 },
+
+ // Multistate output
+ // { Zunk, Cx0013, 0x000E, (MultiOutStateText), Cm1, 0 },
+ { Zstring, Cx0013, 0x001C, Z_(MultiOutDescription), Cm1, 0 },
+ { Zuint16, Cx0013, 0x004A, Z_(MultiOutNumberOfStates),Cm1, 0 },
+ { Zbool, Cx0013, 0x0051, Z_(MultiOutOutOfService), Cm1, 0 },
+ { Zuint16, Cx0013, 0x0055, Z_(MultiOutValue), Cm1, 0 },
+ // { Zunk, Cx0013, 0x0057, (MultiOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx0013, 0x0067, Z_(MultiOutReliability), Cm1, 0 },
+ { Zuint16, Cx0013, 0x0068, Z_(MultiOutRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0013, 0x006F, Z_(MultiOutStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0013, 0x0100, Z_(MultiOutApplicationType),Cm1, 0 },
+
+ // Multistate Value cluster
+ // { Zunk, Cx0014, 0x000E, (MultiStateText), Cm1, 0 },
+ { Zstring, Cx0014, 0x001C, Z_(MultiDescription), Cm1, 0 },
+ { Zuint16, Cx0014, 0x004A, Z_(MultiNumberOfStates), Cm1, 0 },
+ { Zbool, Cx0014, 0x0051, Z_(MultiOutOfService), Cm1, 0 },
+ { Zuint16, Cx0014, 0x0055, Z_(MultiValue), Cm1, 0 },
+ { Zenum8, Cx0014, 0x0067, Z_(MultiReliability), Cm1, 0 },
+ { Zuint16, Cx0014, 0x0068, Z_(MultiRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0014, 0x006F, Z_(MultiStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0014, 0x0100, Z_(MultiApplicationType), Cm1, 0 },
+
+ // Power Profile cluster
+ { Zuint8, Cx001A, 0x0000, Z_(TotalProfileNum), Cm1, 0 },
+ { Zbool, Cx001A, 0x0001, Z_(MultipleScheduling), Cm1, 0 },
+ { Zmap8, Cx001A, 0x0002, Z_(EnergyFormatting), Cm1, 0 },
+ { Zbool, Cx001A, 0x0003, Z_(EnergyRemote), Cm1, 0 },
+ { Zmap8, Cx001A, 0x0004, Z_(ScheduleMode), Cm1, 0 },
+
+ // Poll Control cluster
+ { Zuint32, Cx0020, 0x0000, Z_(CheckinInterval), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0001, Z_(LongPollInterval), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0002, Z_(ShortPollInterval), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0003, Z_(FastPollTimeout), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0004, Z_(CheckinIntervalMin), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0005, Z_(LongPollIntervalMin), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0006, Z_(FastPollTimeoutMax), Cm1, 0 },
+
+ // Green Power
+ // Server attributes
+ { Zuint8, Cx0021, 0x0000, Z_(MaxSinkTableEntries), Cm1, 0 },
+ { Zoctstr16,Cx0021, 0x0001, Z_(SinkTable), Cm1, 0 },
+ { Zmap8, Cx0021, 0x0002, Z_(CommunicationMode), Cm1, 0 },
+ { Zmap8, Cx0021, 0x0003, Z_(CcommissioningExitMode),Cm1, 0 },
+ { Zuint16, Cx0021, 0x0004, Z_(CommissioningWindow), Cm1, 0 },
+ { Zmap8, Cx0021, 0x0005, Z_(SecurityLevel), Cm1, 0 },
+ { Zmap24, Cx0021, 0x0006, Z_(ServerFunctionality), Cm1, 0 },
+ { Zmap24, Cx0021, 0x0007, Z_(ServerActiveFunctionality), Cm1, 0 },
+ { Zuint8, Cx0021, 0x0010, Z_(MaxProxyTableEntries), Cm1, 0 },
+ { Zoctstr16,Cx0021, 0x0011, Z_(ProxyTable), Cm1, 0 },
+ { Zuint8, Cx0021, 0x0012, Z_(NotificationRetryNumber),Cm1, 0 },
+ { Zuint8, Cx0021, 0x0013, Z_(NotificationRetryTimer),Cm1, 0 },
+ { Zuint8, Cx0021, 0x0014, Z_(MaxSearchCounter), Cm1, 0 },
+ { Zoctstr16,Cx0021, 0x0015, Z_(BlockedGPDID), Cm1, 0 },
+ { Zmap24, Cx0021, 0x0016, Z_(ClientFunctionality), Cm1, 0 },
+ { Zmap24, Cx0021, 0x0017, Z_(ClientActiveFunctionality), Cm1, 0 },
+ // Shared by Server and Client
+ { Zmap8, Cx0021, 0x0020, Z_(SharedSecurityKeyType),Cm1, 0 },
+ { Zkey128, Cx0021, 0x0021, Z_(SharedSecurityKey), Cm1, 0 },
+ { Zkey128, Cx0021, 0x0022, Z_(LinkKey), Cm1, 0 },
+
+ // Shade Configuration cluster
+ { Zuint16, Cx0100, 0x0000, Z_(PhysicalClosedLimit), Cm1, 0 },
+ { Zuint8, Cx0100, 0x0001, Z_(MotorStepSize), Cm1, 0 },
+ { Zmap8, Cx0100, 0x0002, Z_(Status), Cm1, 0 },
+ { Zuint16, Cx0100, 0x0010, Z_(ClosedLimit), Cm1, 0 },
+ { Zenum8, Cx0100, 0x0011, Z_(Mode), Cm1, 0 },
+
+ // Door Lock cluster
+ { Zenum8, Cx0101, 0x0000, Z_(LockState), Cm1, 0 },
+ { Zenum8, Cx0101, 0x0001, Z_(LockType), Cm1, 0 },
+ { Zbool, Cx0101, 0x0002, Z_(ActuatorEnabled), Cm1, 0 },
+ { Zenum8, Cx0101, 0x0003, Z_(DoorState), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0004, Z_(DoorOpenEvents), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0005, Z_(DoorClosedEvents), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0006, Z_(OpenPeriod), Cm1, 0 },
+
+ // Door locks
+ { Zuint16, Cx0101, 0x0010, Z_(NumberOfLogRecordsSupported), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0011, Z_(NumberOfTotalUsersSupported), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0012, Z_(NumberOfPINUsersSupported), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0013, Z_(NumberOfRFIDUsersSupported), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0014, Z_(NumberOfWeekDaySchedulesSupportedPerUser), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0015, Z_(NumberOfYearDaySchedulesSupportedPerUser), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0016, Z_(NumberOfHolidaySchedulesSupported), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0017, Z_(MaxPINCodeLength), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0018, Z_(MinPINCodeLength), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0019, Z_(MaxRFIDCodeLength), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0011, Z_(MinRFIDCodeLength), Cm1, 0 },
+ { Zbool, Cx0101, 0x0020, Z_(LockEnableLogging), Cm1, 0 },
+ { Zstring, Cx0101, 0x0021, Z_(LockLanguage), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0022, Z_(LockLEDSettings), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0023, Z_(AutoRelockTime), Cm1, 0 },
+ { Zuint8, Cx0101, 0x0024, Z_(LockSoundVolume), Cm1, 0 },
+ { Zenum8, Cx0101, 0x0025, Z_(LockOperatingMode), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0026, Z_(LockSupportedOperatingModes), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0027, Z_(LockDefaultConfigurationRegister), Cm1, 0 },
+ { Zbool, Cx0101, 0x0028, Z_(LockEnableLocalProgramming), Cm1, 0 },
+ { Zbool, Cx0101, 0x0029, Z_(LockEnableOneTouchLocking), Cm1, 0 },
+ { Zbool, Cx0101, 0x002A, Z_(LockEnableInsideStatusLED), Cm1, 0 },
+ { Zbool, Cx0101, 0x002B, Z_(LockEnablePrivacyModeButton), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0040, Z_(LockAlarmMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0041, Z_(LockKeypadOperationEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0042, Z_(LockRFOperationEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0043, Z_(LockManualOperationEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0044, Z_(LockRFIDOperationEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0045, Z_(LockKeypadProgrammingEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0046, Z_(LockRFProgrammingEventMask), Cm1, 0 },
+ { Zmap16, Cx0101, 0x0047, Z_(LockRFIDProgrammingEventMask), Cm1, 0 },
+ // Aqara Lumi Vibration Sensor
+ { Zuint16, Cx0101, 0x0055, Z_(AqaraVibrationMode), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0503, Z_(AqaraVibrationsOrAngle), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0505, Z_(AqaraVibration505), Cm1, 0 },
+ { Zuint48, Cx0101, 0x0508, Z_(AqaraAccelerometer), Cm1, 0 },
+
+ // Window Covering cluster
+ { Zenum8, Cx0102, 0x0000, Z_(WindowCoveringType), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0001, Z_(PhysicalClosedLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0002, Z_(PhysicalClosedLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0003, Z_(CurrentPositionLift), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0004, Z_(CurrentPositionTilt), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0005, Z_(NumberofActuationsLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0006, Z_(NumberofActuationsTilt),Cm1, 0 },
+ { Zmap8, Cx0102, 0x0007, Z_(ConfigStatus), Cm1, 0 },
+ { Zuint8, Cx0102, 0x0008, Z_(CurrentPositionLiftPercentage),Cm1, 0 },
+ { Zuint8, Cx0102, 0x0009, Z_(CurrentPositionTiltPercentage),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0010, Z_(InstalledOpenLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0011, Z_(InstalledClosedLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0012, Z_(InstalledOpenLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0013, Z_(InstalledClosedLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0014, Z_(VelocityLift), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0015, Z_(AccelerationTimeLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0016, Z_(DecelerationTimeLift), Cm1, 0 },
+ { Zmap8, Cx0102, 0x0017, Z_(Mode), Cm1, 0 },
+ { Zoctstr, Cx0102, 0x0018, Z_(IntermediateSetpointsLift),Cm1, 0 },
+ { Zoctstr, Cx0102, 0x0019, Z_(IntermediateSetpointsTilt),Cm1, 0 },
+ // Tuya specific, from Zigbee2MQTT https://github.com/Koenkk/zigbee-herdsman/blob/4fed7310d1e1e9d81e5148cf5b4d8ec98d03c687/src/zcl/definition/cluster.ts#L1772
+ { Zenum8, Cx0102, 0xF000, Z_(TuyaMovingState),Cm1, 0 },
+ { Zenum8, Cx0102, 0xF001, Z_(TuyaCalibration),Cm1, 0 },
+ { Zenum8, Cx0102, 0xF002, Z_(TuyaMotorReversal),Cm1, 0 },
+ { Zuint16, Cx0102, 0xF003, Z_(TuyaCalibrationTime),Cm1, 0 },
+
+ // Thermostat
+ { Zint16, Cx0201, 0x0000, Z_(LocalTemperature), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature) },
+ { Zint16, Cx0201, 0x0001, Z_(OutdoorTemperature),Cm_100, 0 },
+ { Zmap8, Cx0201, 0x0002, Z_(ThermostatOccupancy), Cm1, 0 },
+ { Zint16, Cx0201, 0x0003, Z_(AbsMinHeatSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0004, Z_(AbsMaxHeatSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0005, Z_(AbsMinCoolSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0006, Z_(AbsMaxCoolSetpointLimit), Cm1, 0 },
+ { Zuint8, Cx0201, 0x0007, Z_(PICoolingDemand), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zuint8, Cx0201, 0x0008, Z_(PIHeatingDemand), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zmap8, Cx0201, 0x0009, Z_(HVACSystemTypeConfiguration), Cm1, 0 },
+ { Zint8, Cx0201, 0x0010, Z_(LocalTemperatureCalibration), Cm_10, 0 },
+ { Zint16, Cx0201, 0x0011, Z_(OccupiedCoolingSetpoint), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ { Zint16, Cx0201, 0x0012, Z_(OccupiedHeatingSetpoint), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ { Zint16, Cx0201, 0x0013, Z_(UnoccupiedCoolingSetpoint), Cm_100, 0 },
+ { Zint16, Cx0201, 0x0014, Z_(UnoccupiedHeatingSetpoint), Cm_100, 0 },
+ { Zint16, Cx0201, 0x0015, Z_(MinHeatSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0016, Z_(MaxHeatSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0017, Z_(MinCoolSetpointLimit), Cm1, 0 },
+ { Zint16, Cx0201, 0x0018, Z_(MaxCoolSetpointLimit), Cm1, 0 },
+ { Zint8, Cx0201, 0x0019, Z_(MinSetpointDeadBand), Cm1, 0 },
+ { Zmap8, Cx0201, 0x001D, Z_(ThermostatAlarmMask), Cm1, 0 },
+ { Zenum8, Cx0201, 0x001E, Z_(ThermostatRunningMode), Cm1, 0 },
+ { Zmap8, Cx0201, 0x001A, Z_(RemoteSensing), Cm1, 0 },
+ { Zenum8, Cx0201, 0x001B, Z_(ControlSequenceOfOperation), Cm1, 0 },
+ { Zenum8, Cx0201, 0x001C, Z_(SystemMode), Cm1, 0 },
+ // below is Eurotronic specific
+ { Zenum8, Cx0201, 0x4000, Z_(TRVMode), Cm1, 0 },
+ { Zuint8, Cx0201, 0x4001, Z_(ValvePosition), Cm1, 0 },
+ { Zuint8, Cx0201, 0x4002, Z_(EurotronicErrors), Cm1, 0 },
+ { Zint16, Cx0201, 0x4003, Z_(CurrentTemperatureSetPoint), Cm_100, 0 },
+ { Zuint24, Cx0201, 0x4008, Z_(EurotronicHostFlags), Cm1, 0 },
+ // below are synthetic virtual attributes used to decode EurotronicHostFlags
+ // Last byte acts as a field mask for the lowest byte value
+ { Zbool, Cx0201, 0xF002, Z_(TRVMirrorDisplay), Cm1, 0 },
+ { Zbool, Cx0201, 0xF004, Z_(TRVBoost), Cm1, 0 },
+ { Zbool, Cx0201, 0xF010, Z_(TRVWindowOpen), Cm1, 0 },
+ { Zbool, Cx0201, 0xF080, Z_(TRVChildProtection), Cm1, 0 },
+ // below are virtual attributes to simplify ZbData import/export
+ { Zuint8, Cx0201, 0xFFF0, Z_(ThSetpoint), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zint16, Cx0201, 0xFFF1, Z_(TempTarget), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+
+ // Fan Control
+ { Zenum8, Cx0202, 0x0000, Z_(FanMode), Cm1, 0 },
+ { Zenum8, Cx0202, 0x0001, Z_(FanModeSequence), Cm1, 0 },
+
+ // Dehumidification Control
+ { Zuint8, Cx0203, 0x0000, Z_(RelativeHumidity), Cm1, 0 },
+ { Zuint8, Cx0203, 0x0001, Z_(DehumidificationCooling), Cm1, 0 },
+ { Zuint8, Cx0203, 0x0010, Z_(RHDehumidificationSetpoint), Cm1, 0 },
+ { Zenum8, Cx0203, 0x0011, Z_(RelativeHumidityMode), Cm1, 0 },
+ { Zenum8, Cx0203, 0x0012, Z_(DehumidificationLockout), Cm1, 0 },
+ { Zuint8, Cx0203, 0x0013, Z_(DehumidificationHysteresis), Cm1, 0 },
+ { Zuint8, Cx0203, 0x0014, Z_(DehumidificationMaxCool), Cm1, 0 },
+ { Zenum8, Cx0203, 0x0015, Z_(RelativeHumidityDisplay), Cm1, 0 },
+
+ // Thermostat User Interface Con- figuration
+ { Zenum8, Cx0204, 0x0000, Z_(TemperatureDisplayMode), Cm1, 0 },
+ { Zenum8, Cx0204, 0x0001, Z_(ThermostatKeypadLockout), Cm1, 0 },
+ { Zenum8, Cx0204, 0x0002, Z_(ThermostatScheduleProgrammingVisibility), Cm1, 0 },
+
+ // Color Control cluster
+ { Zuint8, Cx0300, 0x0000, Z_(Hue), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, hue) },
+ { Zuint8, Cx0300, 0x0001, Z_(Sat), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, sat) },
+ { Zuint16, Cx0300, 0x0002, Z_(RemainingTime), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0003, Z_(X), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, x) },
+ { Zuint16, Cx0300, 0x0004, Z_(Y), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, y) },
+ { Zenum8, Cx0300, 0x0005, Z_(DriftCompensation), Cm1, 0 },
+ { Zstring, Cx0300, 0x0006, Z_(CompensationText), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0007, Z_(CT), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, ct) },
+ { Zenum8, Cx0300, 0x0008, Z_(ColorMode), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, colormode) },
+ { Zuint8, Cx0300, 0x0010, Z_(NumberOfPrimaries), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0011, Z_(Primary1X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0012, Z_(Primary1Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0013, Z_(Primary1Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0015, Z_(Primary2X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0016, Z_(Primary2Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0017, Z_(Primary2Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0019, Z_(Primary3X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x001A, Z_(Primary3Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x001B, Z_(Primary3Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0020, Z_(Primary4X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0021, Z_(Primary4Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0022, Z_(Primary4Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0024, Z_(Primary5X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0025, Z_(Primary5Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0026, Z_(Primary5Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0028, Z_(Primary6X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0029, Z_(Primary6Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x002A, Z_(Primary6Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0030, Z_(WhitePointX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0031, Z_(WhitePointY), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0032, Z_(ColorPointRX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0033, Z_(ColorPointRY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0034, Z_(ColorPointRIntensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0036, Z_(ColorPointGX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0037, Z_(ColorPointGY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0038, Z_(ColorPointGIntensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x003A, Z_(ColorPointBX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x003B, Z_(ColorPointBY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x003C, Z_(ColorPointBIntensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x4000, Z_(EnhancedCurrentHue), Cm1, 0 },
+ { Zenum8, Cx0300, 0x4001, Z_(EnhancedColorMode), Cm1, 0 },
+ { Zuint8, Cx0300, 0x4002, Z_(ColorLoopActive), Cm1, 0 },
+ { Zuint8, Cx0300, 0x4003, Z_(ColorLoopDirection), Cm1, 0 },
+ { Zuint16, Cx0300, 0x4004, Z_(ColorLoopTime), Cm1, 0 },
+ { Zuint16, Cx0300, 0x4005, Z_(ColorLoopStartEnhancedHue), Cm1, 0 },
+ { Zuint16, Cx0300, 0x4006, Z_(ColorLoopStoredEnhancedHue), Cm1, 0 },
+ { Zmap16, Cx0300, 0x400A, Z_(ColorCapabilities), Cm1, 0 },
+ { Zuint16, Cx0300, 0x400B, Z_(ColorTempPhysicalMinMireds), Cm1, 0 },
+ { Zuint16, Cx0300, 0x400C, Z_(ColorTempPhysicalMaxMireds), Cm1, 0 },
+ { Zuint16, Cx0300, 0x4010, Z_(ColorStartUpColorTempireds), Cm1, 0 },
+
+ // Ballast Configuration
+ { Zuint8, Cx0301, 0x0000, Z_(BallastPhysicalMinLevel), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0001, Z_(BallastPhysicalMaxLevel), Cm1, 0 },
+ { Zmap8, Cx0301, 0x0002, Z_(BallastStatus), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0010, Z_(BallastMinLevel), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0011, Z_(BallastMaxLevel), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0012, Z_(BallastPowerOnLevel), Cm1, 0 },
+ { Zuint16, Cx0301, 0x0013, Z_(BallastPowerOnFadeTime), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0014, Z_(IntrinsicBallastFactor), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0015, Z_(BallastFactorAdjustment), Cm1, 0 },
+ { Zuint8, Cx0301, 0x0020, Z_(BallastLampQuantity), Cm1, 0 },
+ { Zstring, Cx0301, 0x0030, Z_(LampType), Cm1, 0 },
+ { Zstring, Cx0301, 0x0031, Z_(LampManufacturer), Cm1, 0 },
+ { Zuint24, Cx0301, 0x0032, Z_(LampRatedHours), Cm1, 0 },
+ { Zuint24, Cx0301, 0x0033, Z_(LampBurnHours), Cm1, 0 },
+ { Zmap8, Cx0301, 0x0034, Z_(LampAlarmMode), Cm1, 0 },
+ { Zuint24, Cx0301, 0x0035, Z_(LampBurnHoursTripPoint), Cm1, 0 },
+
+ // Illuminance Measurement cluster
+ { Zuint16, Cx0400, 0x0000, Z_(Illuminance), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_PIR, illuminance) }, // Illuminance (in Lux)
+ { Zuint16, Cx0400, 0x0001, Z_(IlluminanceMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0400, 0x0002, Z_(IlluminanceMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0400, 0x0003, Z_(IlluminanceTolerance), Cm1, 0 }, //
+ { Zenum8, Cx0400, 0x0004, Z_(IlluminanceLightSensorType), Cm1, 0 }, //
+ // { Zunk, Cx0400, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Illuminance Level Sensing cluster
+ { Zenum8, Cx0401, 0x0000, Z_(IlluminanceLevelStatus), Cm1, 0 }, // Illuminance (in Lux)
+ { Zenum8, Cx0401, 0x0001, Z_(IlluminanceLightSensorType), Cm1, 0 }, // LightSensorType
+ { Zuint16, Cx0401, 0x0010, Z_(IlluminanceTargetLevel), Cm1, 0 }, //
+ // { Zunk, Cx0401, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Temperature Measurement cluster
+ { Zint16, Cx0402, 0x0000, Z_(Temperature), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, temperature) },
+ { Zint16, Cx0402, 0x0001, Z_(TemperatureMinMeasuredValue), Cm_100, 0 }, //
+ { Zint16, Cx0402, 0x0002, Z_(TemperatureMaxMeasuredValue), Cm_100, 0 }, //
+ { Zuint16, Cx0402, 0x0003, Z_(TemperatureTolerance), Cm_100, 0 }, //
+ // { Zunk, Cx0402, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Pressure Measurement cluster
+ { Zint16, Cx0403, 0x0000, Z_(Pressure), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, pressure) }, // Pressure
+ { Zint16, Cx0403, 0x0001, Z_(PressureMinMeasuredValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0002, Z_(PressureMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0403, 0x0003, Z_(PressureTolerance), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0010, Z_(PressureScaledValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0011, Z_(PressureMinScaledValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0012, Z_(PressureMaxScaledValue), Cm1, 0 }, //
+ { Zuint16, Cx0403, 0x0013, Z_(PressureScaledTolerance), Cm1, 0 }, //
+ { Zint8, Cx0403, 0x0014, Z_(PressureScale), Cm1, 0 }, //
+ { Zint16, Cx0403, 0xFFF0, Z_(SeaPressure), Cm1, Z_MAPPING(Z_Data_Thermo, pressure) }, // Pressure at Sea Level, Tasmota specific
+ // { Zunk, Cx0403, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other Pressure values
+
+ // Flow Measurement cluster
+ { Zuint16, Cx0404, 0x0000, Z_(FlowRate), Cm_10, 0 }, // Flow (in m3/h)
+ { Zuint16, Cx0404, 0x0001, Z_(FlowMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0404, 0x0002, Z_(FlowMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0404, 0x0003, Z_(FlowTolerance), Cm1, 0 }, //
+ // { Zunk, Cx0404, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Relative Humidity Measurement cluster
+ { Zuint16, Cx0405, 0x0000, Z_(Humidity), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, humidity) }, // Humidity
+ { Zuint16, Cx0405, 0x0001, Z_(HumidityMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0405, 0x0002, Z_(HumidityMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0405, 0x0003, Z_(HumidityTolerance), Cm1, 0 }, //
+ // { Zunk, Cx0405, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Occupancy Sensing cluster
+ { Zmap8, Cx0406, 0x0000, Z_(Occupancy), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_PIR, occupancy) }, // Occupancy (map8)
+ { Zenum8, Cx0406, 0x0001, Z_(OccupancySensorType), Cm1, 0 }, // OccupancySensorType
+ { Zuint16, Cx0406, 0x0010, Z_(PIROccupiedToUnoccupiedDelay), Cm1, 0 },
+ { Zuint16, Cx0406, 0x0011, Z_(PIRUnoccupiedToOccupiedDelay), Cm1, 0 },
+ { Zuint8, Cx0406, 0x0012, Z_(PIRUnoccupiedToOccupiedThreshold), Cm1, 0 },
+ // { Zunk, Cx0406, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // IAS Cluster (Intruder Alarm System)
+ { Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8)
+ { Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor
+ { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor
+ { ZEUI64, Cx0500, 0x0010, Z_(IASCIEAddress), Cm1, 0 },
+ { Zuint8, Cx0500, 0x0011, Z_(ZoneID), Cm1, 0 },
+ { Zuint8, Cx0500, 0x0012, Z_(NumberOfZoneSensitivityLevelsSupported), Cm1, 0 },
+ { Zuint8, Cx0500, 0x0013, Z_(CurrentZoneSensitivityLevel), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_CIE, Z_(CIE), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_PIR, Z_(Occupancy), Cm1, 0 }, // normally converted to the actual Occupancy 0406/0000
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Contact, Z_(Contact), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // We fit the first bit in the LSB
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Fire, Z_(Fire), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Water, Z_(Water), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_CO, Z_(CO), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Personal, Z_(PersonalAlarm),Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Movement, Z_(Movement), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Panic, Z_(Panic), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_GlassBreak, Z_(GlassBreak),Cm1, 0 },
+
+ // Metering (Smart Energy) cluster
+ { Zuint48, Cx0702, 0x0000, Z_(EnergyTotal), Cm1, 0 },
+
+ // Meter Identification cluster
+ { Zstring, Cx0B01, 0x0000, Z_(CompanyName), Cm1, 0 },
+ { Zuint16, Cx0B01, 0x0001, Z_(MeterTypeID), Cm1, 0 },
+ { Zuint16, Cx0B01, 0x0004, Z_(DataQualityID), Cm1, 0 },
+ { Zstring, Cx0B01, 0x0005, Z_(CustomerName), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0006, Z_(Model), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0007, Z_(PartNumber), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0008, Z_(ProductRevision), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x000A, Z_(SoftwareRevision), Cm1, 0 },
+ { Zstring, Cx0B01, 0x000B, Z_(UtilityName), Cm1, 0 },
+ { Zstring, Cx0B01, 0x000C, Z_(POD), Cm1, 0 },
+ { Zint24, Cx0B01, 0x000D, Z_(AvailablePower), Cm1, 0 },
+ { Zint24, Cx0B01, 0x000E, Z_(PowerThreshold), Cm1, 0 },
+
+ // Electrical Measurement cluster
+ { Zmap32, Cx0B04, 0x0000, Z_(ElectricalMeasurementType), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0100, Z_(DCVoltage), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0101, Z_(DCVoltageMin), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0102, Z_(DCVoltageMax), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0103, Z_(DCCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0104, Z_(DCCurrentMin), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0105, Z_(DCCurrentMax), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0106, Z_(DCPower), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0107, Z_(DCPowerMin), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0108, Z_(DCPowerMax), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0200, Z_(DCVoltageMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0201, Z_(DCVoltageDivisor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0202, Z_(DCCurrentMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0203, Z_(DCCurrentDivisor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0204, Z_(DCPowerMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0205, Z_(DCPowerDivisor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0300, Z_(ACFrequency), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0301, Z_(ACFrequencyMin), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0302, Z_(ACFrequencyMax), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0303, Z_(NeutralCurrent), Cm1, 0 },
+ { Zint32, Cx0B04, 0x0304, Z_(TotalActivePower), Cm1, 0 },
+ { Zint32, Cx0B04, 0x0305, Z_(TotalReactivePower), Cm1, 0 },
+ { Zuint32, Cx0B04, 0x0306, Z_(TotalApparentPower), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0307, Z_(Measured1stHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0308, Z_(Measured3rdHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0309, Z_(Measured5thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030A, Z_(Measured7thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030B, Z_(Measured9thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030C, Z_(Measured11thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030D, Z_(MeasuredPhase1stHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030E, Z_(MeasuredPhase3rdHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x030F, Z_(MeasuredPhase5thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0310, Z_(MeasuredPhase7thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0311, Z_(MeasuredPhase9thHarmonicCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0312, Z_(MeasuredPhase11thHarmonicCurrent), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0400, Z_(ACFrequencyMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0401, Z_(ACFrequencyDivisor), Cm1, 0 },
+ { Zuint32, Cx0B04, 0x0402, Z_(PowerMultiplier), Cm1, 0 },
+ { Zuint32, Cx0B04, 0x0403, Z_(PowerDivisor), Cm1, 0 },
+ { Zint8, Cx0B04, 0x0404, Z_(HarmonicCurrentMultiplier), Cm1, 0 },
+ { Zint8, Cx0B04, 0x0405, Z_(PhaseHarmonicCurrentMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0501, Z_(LineCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0502, Z_(ActiveCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0503, Z_(ReactiveCurrent), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0505, Z_(RMSVoltage), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Plug, mains_voltage) },
+ { Zuint16, Cx0B04, 0x0506, Z_(RMSVoltageMin), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0507, Z_(RMSVoltageMax), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0508, Z_(RMSCurrent), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0509, Z_(RMSCurrentMin), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x050A, Z_(RMSCurrentMax), Cm1, 0 },
+ { Zint16, Cx0B04, 0x050B, Z_(ActivePower), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Plug, mains_power) },
+ { Zuint16, Cx0B04, 0x050C, Z_(ActivePowerMin), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x050D, Z_(ActivePowerMax), Cm1, 0 },
+ { Zint16, Cx0B04, 0x050E, Z_(ReactivePower), Cm1, 0 },
+ { Zint16, Cx0B04, 0x050F, Z_(ApparentPower), Cm1, 0 },
+ { Zint8, Cx0B04, 0x0510, Z_(PowerFactor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0511, Z_(AverageRMSVoltageMeasurementPeriod), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0512, Z_(AverageRMSOverVoltageCounter), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0513, Z_(AverageRMSUnderVoltageCounter), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0514, Z_(RMSExtremeOverVoltagePeriod), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0515, Z_(RMSExtremeUnderVoltagePeriod), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0516, Z_(RMSVoltageSagPeriod), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0517, Z_(RMSVoltageSwellPeriod), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0600, Z_(ACVoltageMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0601, Z_(ACVoltageDivisor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0602, Z_(ACCurrentMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0603, Z_(ACCurrentDivisor), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0604, Z_(ACPowerMultiplier), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0605, Z_(ACPowerDivisor), Cm1, 0 },
+ { Zmap8, Cx0B04, 0x0700, Z_(DCOverloadAlarmsMask), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0701, Z_(DCVoltageOverload), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0702, Z_(DCCurrentOverload), Cm1, 0 },
+ { Zmap16, Cx0B04, 0x0800, Z_(ACAlarmsMask), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0801, Z_(ACVoltageOverload), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0802, Z_(ACCurrentOverload), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0803, Z_(ACActivePowerOverload), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0804, Z_(ACReactivePowerOverload), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0805, Z_(AverageRMSOverVoltage), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0806, Z_(AverageRMSUnderVoltage), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0807, Z_(RMSExtremeOverVoltage), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0808, Z_(RMSExtremeUnderVoltage), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0809, Z_(RMSVoltageSag), Cm1, 0 },
+ { Zint16, Cx0B04, 0x080A, Z_(RMSVoltageSwell), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0901, Z_(LineCurrentPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0902, Z_(ActiveCurrentPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0903, Z_(ReactiveCurrentPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0905, Z_(RMSVoltagePhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0906, Z_(RMSVoltageMinPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0907, Z_(RMSVoltageMaxPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0908, Z_(RMSCurrentPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0909, Z_(RMSCurrentMinPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x090A, Z_(RMSCurrentMaxPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x090B, Z_(ActivePowerPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x090C, Z_(ActivePowerMinPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x090D, Z_(ActivePowerMaxPhB), Cm1, 0 },
+ { Zint16, Cx0B04, 0x090E, Z_(ReactivePowerPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x090F, Z_(ApparentPowerPhB), Cm1, 0 },
+ { Zint8, Cx0B04, 0x0910, Z_(PowerFactorPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0911, Z_(AverageRMSVoltageMeasurementPeriodPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0912, Z_(AverageRMSOverVoltageCounterPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0913, Z_(AverageRMSUnderVoltageCounterPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0914, Z_(RMSExtremeOverVoltagePeriodPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0915, Z_(RMSExtremeUnderVoltagePeriodPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0916, Z_(RMSVoltageSagPeriodPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0917, Z_(RMSVoltageSwellPeriodPhB), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A01, Z_(LineCurrentPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A02, Z_(ActiveCurrentPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A03, Z_(ReactiveCurrentPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A05, Z_(RMSVoltagePhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A06, Z_(RMSVoltageMinPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A07, Z_(RMSVoltageMaxPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A08, Z_(RMSCurrentPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A09, Z_(RMSCurrentMinPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A0A, Z_(RMSCurrentMaxPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A0B, Z_(ActivePowerPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A0C, Z_(ActivePowerMinPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A0D, Z_(ActivePowerMaxPhC), Cm1, 0 },
+ { Zint16, Cx0B04, 0x0A0E, Z_(ReactivePowerPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A0F, Z_(ApparentPowerPhC), Cm1, 0 },
+ { Zint8, Cx0B04, 0x0A10, Z_(PowerFactorPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A11, Z_(AverageRMSVoltageMeasurementPeriodPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A12, Z_(AverageRMSOverVoltageCounterPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A13, Z_(AverageRMSUnderVoltageCounterPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A14, Z_(RMSExtremeOverVoltagePeriodPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A15, Z_(RMSExtremeUnderVoltagePeriodPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A16, Z_(RMSVoltageSagPeriodPhC), Cm1, 0 },
+ { Zuint16, Cx0B04, 0x0A17, Z_(RMSVoltageSwellPeriodPhC), Cm1, 0 },
+
+ // Diagnostics cluster
+ { Zuint16, Cx0B05, 0x0000, Z_(NumberOfResets), Cm1, 0 },
+ { Zuint16, Cx0B05, 0x0001, Z_(PersistentMemoryWrites),Cm1, 0 },
+ { Zuint8, Cx0B05, 0x011C, Z_(LastMessageLQI), Cm1, 0 },
+ { Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), Cm1, 0 },
+
+ // Tuya Moes specific - 0xEF00
+ // Mapping of Tuya type with internal mapping
+ // 0x00 - Zoctstr (len N)
+ // 0x01 - Ztuya1 (len 1) - equivalent to Zuint8 without invalid value handling
+ // 0x02 - Ztuya4 (len 4) - equivalent to Zint32 in big endian and without invalid value handling
+ // 0x03 - Zstr (len N)
+ // 0x04 - Ztuya1 (len 1)
+ // 0x05 - Ztuya4u (len 1/2/4) - equivalent to Zuint32
+ // { Ztuya0, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
+ // { Ztuya0, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0101, Z_(Power), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0102, Z_(Power2), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0103, Z_(Power3), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0104, Z_(Power4), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0107, Z_(TuyaChildLock), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
+ // { Zint16, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ // { Zint16, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature) }, // will be overwritten by actual LocalTemperature
+ // { Zuint8, CxEF00, 0x0203, Z_(Dimmer), Cm1, Z_MAPPING(Z_Data_Light, dimmer) }, // will be overwritten by actual LocalTemperature
+ // { Zmap8, CxEF00, 0x0203, Z_(Occupancy), Cm1, Z_MAPPING(Z_Data_PIR, occupancy) }, // will be overwritten by actual LocalTemperature
+ // { Ztuya2, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
+ // { Ztuya2, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
+ // { Ztuya2, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
+ // { Ztuya2, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
+ // { Ztuya2, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
+ // { Ztuya2, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
+ // { Zuint8, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ // { Ztuya2, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
+ // { Ztuya2, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
+ // { Ztuya4, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
+ // { Ztuya4, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
+ // { Ztuya4, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
+ // { Ztuya4, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
+
+ // Legrand BTicino - Manuf code 0x1021
+ { Zdata16, CxFC01, 0x0000, Z_(LegrandOpt1), Cm1, 0 },
+ { Zbool, CxFC01, 0x0001, Z_(LegrandOpt2), Cm1, 0 },
+ { Zbool, CxFC01, 0x0002, Z_(LegrandOpt3), Cm1, 0 },
+
+ // Legrand - Manuf code 0x1021
+ { Zenum8, CxFC40, 0x0000, Z_(LegrandHeatingMode), Cm1, 0 },
+
+ // Aqara Opple spacific
+ { Zuint8, CxFCC0, 0x0009, Z_(OppleMode), Cm1, 0 },
+
+ // Terncy specific - 0xFCCC
+ { Zuint16, CxFCCC, 0x001A, Z_(TerncyDuration), Cm1, 0 },
+ { Zint16, CxFCCC, 0x001B, Z_(TerncyRotate), Cm1, 0 },
+};
+#pragma GCC diagnostic pop
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
similarity index 55%
rename from tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino
rename to tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
index 94095188d..4614011aa 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
@@ -19,653 +19,6 @@
#ifdef USE_ZIGBEE
-/*********************************************************************************************\
- * ZCL
-\*********************************************************************************************/
-
-
-enum Z_DataTypes {
- Znodata = 0x00,
- Zdata8 = 0x08, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
- Zbool = 0x10,
- Zmap8 = 0x18, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
- Zuint8 = 0x20, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
- Zint8 = 0x28, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
- Zenum8 = 0x30, Zenum16 = 0x31,
- Zsemi = 0x38, Zsingle = 0x39, Zdouble = 0x3A,
- Zoctstr = 0x41, Zstring = 0x42, Zoctstr16 = 0x43, Zstring16 = 0x44,
- Arrray = 0x48,
- Zstruct = 0x4C,
- Zset = 0x50, Zbag = 0x51,
- ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
- ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
- ZEUI64 = 0xF0, Zkey128 = 0xF1,
- Zunk = 0xFF,
- // adding fake type for Tuya specific encodings
- Ztuya0 = Zoctstr,
- Ztuya1 = Zbool,
- Ztuya2 = Zint32,
- Ztuya3 = Zstring,
- Ztuya4 = Zuint8,
- Ztuya5 = Zuint32
-};
-
-//
-// get the lenth in bytes for a data-type
-// return 0 if unknown of type specific
-//
-// Note: this code is smaller than a static array
-uint8_t Z_getDatatypeLen(uint8_t t) {
- if ( ((t >= 0x08) && (t <= 0x0F)) || // data8 - data64
- ((t >= 0x18) && (t <= 0x2F)) ) { // map/uint/int
- return (t & 0x07) + 1;
- }
- switch (t) {
- case Zbool:
- case Zenum8:
- return 1;
- case Zenum16:
- case Zsemi:
- case ZclusterId:
- case ZattribId:
- return 2;
- case Zsingle:
- case ZToD:
- case Zdate:
- case ZUTC:
- case ZbacOID:
- return 4;
- case Zdouble:
- case ZEUI64:
- return 8;
- case Zkey128:
- return 16;
- case Znodata:
- default:
- return 0;
- }
-}
-
-// is the type a discrete type, cf. section 2.6.2 of ZCL spec
-bool Z_isDiscreteDataType(uint8_t t) {
- if ( ((t >= 0x20) && (t <= 0x2F)) || // uint8 - int64
- ((t >= 0x38) && (t <= 0x3A)) || // semi - double
- ((t >= 0xE0) && (t <= 0xE2)) ) { // ToD - UTC
- return false;
- } else {
- return true;
- }
-}
-
-typedef struct Z_AttributeConverter {
- uint8_t type;
- uint8_t cluster_short;
- uint16_t attribute;
- uint16_t name_offset;
- uint8_t multiplier_idx; // multiplier index for numerical value, use CmToMultiplier(), (if > 0 multiply by x, if <0 device by x)
- // the high 4 bits are used to encode flags
- // currently: 0x80 = this parameter needs to be exported to ZbData
- uint8_t mapping; // high 4 bits = type, low 4 bits = offset in bytes from header
- // still room for a byte
-} Z_AttributeConverter;
-
-// Get offset in bytes of attributes, starting after the header (skipping first 4 bytes)
-#define Z_OFFSET(c,a) (offsetof(class c, a) - sizeof(Z_Data))
-#define Z_CLASS(c) c // necessary to get a valid token without concatenation (which wouldn't work)
-#define Z_MAPPING(c,a) (((((uint8_t)Z_CLASS(c)::type) & 0x0F) << 4) | Z_OFFSET(c,a))
-
-// lines with this marker, will be used to export automatically data to `ZbData`
-// at the condition Z_MAPPING() is also used
-const uint8_t Z_EXPORT_DATA = 0x80;
-
-// Cluster numbers are store in 8 bits format to save space,
-// the following tables allows the conversion from 8 bits index Cx...
-// to the 16 bits actual cluster number
-enum Cx_cluster_short {
- Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007,
- Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,
- Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100,
- Cx0101, Cx0102, Cx0201, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403,
- Cx0404, Cx0405, Cx0406, Cx0500, Cx0702, Cx0B01, Cx0B04, Cx0B05,
- CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
-};
-
-const uint16_t Cx_cluster[] PROGMEM = {
- 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
- 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100,
- 0x0101, 0x0102, 0x0201, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403,
- 0x0404, 0x0405, 0x0406, 0x0500, 0x0702, 0x0B01, 0x0B04, 0x0B05,
- 0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
-};
-
-uint16_t CxToCluster(uint8_t cx) {
- if (cx < nitems(Cx_cluster)) {
- return pgm_read_word(&Cx_cluster[cx]);
- }
- return 0xFFFF;
-}
-
-uint8_t ClusterToCx(uint16_t cluster) {
- for (uint32_t i=0; iname_offset)) { continue; } // avoid strcasecmp_P() from crashing
- if (0 == strcasecmp_P(command, Z_strings + pgm_read_word(&converter->name_offset))) {
- if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); }
- if (attribute) { *attribute = pgm_read_word(&converter->attribute); }
- if (multiplier) { *multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx)); }
- if (zigbee_type) { *zigbee_type = pgm_read_byte(&converter->type); }
- uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
- if (data_type) { *data_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4); }
- if (map_offset) { *map_offset = (conv_mapping & 0x0F); }
- return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
+//
+// Find attribute matcher by name
+//
+class Z_attribute_match Z_findAttributeMatcherByName(uint16_t shortaddr, const char *name) {
+ const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+ // works even if the device is unknown
+
+ Z_attribute_match matched_attr = Z_plugin_matchAttributeByName(device.modelId, device.manufacturerId, name);
+ if (!matched_attr.found()) {
+ for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
+ const Z_AttributeConverter *converter = &Z_PostProcess[i];
+ if (0 == pgm_read_word(&converter->name_offset)) { continue; } // avoid strcasecmp_P() from crashing
+ if (0 == strcasecmp_P(name, Z_strings + pgm_read_word(&converter->name_offset))) {
+ matched_attr.cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
+ matched_attr.attribute = pgm_read_word(&converter->attribute);
+ matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
+ int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ if (multiplier8 > 1) { matched_attr.multiplier = multiplier8; }
+ if (multiplier8 < 0) { matched_attr.divider = -multiplier8; }
+ matched_attr.zigbee_type = pgm_read_byte(&converter->type);
+ uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
+ matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
+ matched_attr.map_offset = (conv_mapping & 0x0F);
+ break;
+ }
}
}
- return nullptr;
+ return matched_attr;
}
//
-// Find attribute details: Name, Type, Multiplier by cuslter/attr_id
+// Find attribute matcher by name
//
-const __FlashStringHelper* zigbeeFindAttributeById(uint16_t cluster, uint16_t attr_id,
- uint8_t *attr_type, int8_t *multiplier) {
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
+// attr_glob: do Attr marked as 0xFFFF match any attribute
+class Z_attribute_match Z_findAttributeMatcherById(uint16_t shortaddr, uint16_t cluster, uint16_t attr_id, bool attr_glob) {
+ const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+ // works even if the device is unknown
- if ((conv_cluster == cluster) && (conv_attr_id == attr_id)) {
- if (multiplier) { *multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx)); }
- if (attr_type) { *attr_type = pgm_read_byte(&converter->type); }
- return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
+ Z_attribute_match matched_attr = Z_plugin_matchAttributeById(device.modelId, device.manufacturerId, cluster, attr_id);
+ if (!matched_attr.found()) {
+ for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
+ const Z_AttributeConverter *converter = &Z_PostProcess[i];
+ uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
+ uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
+
+ if ((conv_cluster == cluster) && (conv_attr_id == attr_id || (attr_glob && conv_attr_id == 0xFFFF))) {
+ matched_attr.cluster = cluster;
+ matched_attr.attribute = attr_id;
+ matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
+ int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ if (multiplier8 > 1) { matched_attr.multiplier = multiplier8; }
+ if (multiplier8 < 0) { matched_attr.divider = -multiplier8; }
+ matched_attr.zigbee_type = pgm_read_byte(&converter->type);
+ uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
+ matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
+ matched_attr.map_offset = (conv_mapping & 0x0F);
+ break;
+ }
}
}
- return nullptr;
+ return matched_attr;
}
class ZCLFrame {
@@ -734,10 +108,11 @@ public:
manuf(manuf_code), transactseq(transact_seq), cmd(cmd_id),
payload(buf_len ? buf_len : 250), // allocate the data frame from source or preallocate big enough
cluster(clusterid), groupaddr(groupaddr),
- shortaddr(srcaddr), _srcendpoint(srcendpoint), dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast),
+ shortaddr(srcaddr), srcendpoint(srcendpoint), dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast),
_linkquality(linkquality), _securityuse(securityuse), _seqnumber(seqnumber)
{
_frame_control.d8 = frame_control;
+ direction = _frame_control.b.direction;
clusterSpecific = (_frame_control.b.frame_type != 0);
needResponse = !_frame_control.b.disable_def_resp;
payload.addBuffer(buf, buf_len);
@@ -754,10 +129,10 @@ public:
"\"manuf\":\"0x%04X\",\"transact\":%d,"
"\"cmdid\":\"0x%02X\",\"payload\":\"%_B\"}}"),
groupaddr, cluster, shortaddr,
- _srcendpoint, dstendpoint, _wasbroadcast,
+ srcendpoint, dstendpoint, _wasbroadcast,
_linkquality, _securityuse, _seqnumber,
_frame_control,
- _frame_control.b.frame_type, _frame_control.b.direction, _frame_control.b.disable_def_resp,
+ _frame_control.b.frame_type, direction, _frame_control.b.disable_def_resp,
manuf, transactseq, cmd,
&payload);
if (Settings->flag3.tuya_serial_mqtt_publish) {
@@ -791,20 +166,20 @@ public:
return zcl_frame;
}
- bool isClusterSpecificCommand(void) {
- return _frame_control.b.frame_type & 1;
- }
+ bool isClusterSpecificCommand(void) const { return _frame_control.b.frame_type & 1; }
+ uint8_t getDirection(void) const { return direction; }
// parsers for received messages
void parseReportAttributes(Z_attribute_list& attr_list);
void generateSyntheticAttributes(Z_attribute_list& attr_list);
void removeInvalidAttributes(Z_attribute_list& attr_list);
+ void applySynonymAttributes(Z_attribute_list& attr_list);
void computeSyntheticAttributes(Z_attribute_list& attr_list);
void generateCallBacks(Z_attribute_list& attr_list);
- void parseReadAttributes(Z_attribute_list& attr_list);
+ void parseReadAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
void parseReadAttributesResponse(Z_attribute_list& attr_list);
- void parseReadConfigAttributes(Z_attribute_list& attr_list);
- void parseConfigAttributes(Z_attribute_list& attr_list);
+ void parseReadConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
+ void parseConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
void parseWriteAttributesResponse(Z_attribute_list& attr_list);
void parseResponse(void);
void parseResponse_inner(uint8_t cmd, bool cluster_specific, uint8_t status);
@@ -833,7 +208,7 @@ public:
inline uint16_t getClusterId(void) const { return cluster; }
inline uint8_t getLinkQuality(void) const { return _linkquality; }
inline uint8_t getCmdId(void) const { return cmd; }
- inline uint16_t getSrcEndpoint(void) const { return _srcendpoint; }
+ inline uint16_t getSrcEndpoint(void) const { return srcendpoint; }
const SBuffer &getPayload(void) const { return payload; }
uint16_t getManufCode(void) const { return manuf; }
@@ -860,9 +235,11 @@ public:
bool direct = false; // true if direct, false if discover router
bool transacSet = false; // is transac already set
+ uint8_t srcendpoint = 0x00; // 0x00 is invalid for the src endpoint
+ bool direction = false; // false = client to server (default), true = server to client (rare)
+
// below private attributes are not used when sending a message
private:
- uint8_t _srcendpoint = 0x00; // 0x00 is invalid for the src endpoint
ZCLHeaderFrameControl_t _frame_control = { .d8 = 0 };
bool _wasbroadcast = false;
uint8_t _linkquality = 0x00;
@@ -902,10 +279,12 @@ uint8_t toPercentageCR2032(uint32_t voltage) {
// Adds to buf:
// - n bytes: value (typically between 1 and 4 bytes, or bigger for strings)
// returns number of bytes of attribute, or <0 if error
+// If the value is `NAN`, the value encoded is the "zigbee invalid value"
int32_t encodeSingleAttribute(SBuffer &buf, double val_d, const char *val_str, uint8_t attrtype) {
uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute length, overloaded for variable length attributes
- uint32_t u32 = val_d;
- int32_t i32 = val_d;
+ bool nan = isnan(val_d);
+ uint32_t u32 = nan ? 0xFFFFFFFF : roundf(val_d);
+ int32_t i32 = roundf(val_d);
float f32 = val_d;
switch (attrtype) {
@@ -939,13 +318,13 @@ int32_t encodeSingleAttribute(SBuffer &buf, double val_d, const char *val_str, u
// signed 8
case Zint8: // int8
- buf.add8(i32);
+ buf.add8(nan ? 0x80 : i32);
break;
case Zint16: // int16
- buf.add16(i32);
+ buf.add16(nan ? 0x8000 : i32);
break;
case Zint32: // int32
- buf.add32(i32);
+ buf.add32(nan ? 0x80000000 : i32);
break;
case Zsingle: // float
@@ -1157,6 +536,16 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
attr.setUInt(uint16_val);
}
break;
+ case Zdata24: // data16
+ case Zmap24: // map16
+ {
+ uint32_t uint32_val = buf.get16(i);
+ uint8_t high = buf.get8(i+2);
+ uint32_val = uint32_val | (high << 16);
+ // i += 3;
+ attr.setUInt(uint32_val);
+ }
+ break;
case Zdata32: // data32
case Zmap32: // map32
{
@@ -1175,7 +564,7 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
}
break;
- // TODO
+ // All other fixed size, convert to a HEX dump
case ZToD: // ToD
case Zdate: // date
case ZclusterId: // clusterId
@@ -1184,17 +573,19 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
case ZEUI64: // EUI64
case Zkey128: // key128
case Zsemi: // semi (float on 2 bytes)
+ {
+ attr.setBuf(buf, i, len);
+ }
+ // i += 16;
break;
// Other un-implemented data types
- case Zdata24: // data24
case Zdata40: // data40
case Zdata48: // data48
case Zdata56: // data56
case Zdata64: // data64
break;
// map
- case Zmap24: // map24
case Zmap40: // map40
case Zmap48: // map48
case Zmap56: // map56
@@ -1247,7 +638,7 @@ void ZCLFrame::parseReportAttributes(Z_attribute_list& attr_list) {
ZCLFrame zcl(2); // message is 2 bytes
zcl.shortaddr = shortaddr;
zcl.cluster = cluster;
- zcl.dstendpoint = _srcendpoint;
+ zcl.dstendpoint = srcendpoint;
zcl.cmd = ZCL_DEFAULT_RESPONSE;
zcl.manuf = manuf;
zcl.clusterSpecific = false; /* not cluster specific */
@@ -1266,8 +657,8 @@ void ZCLFrame::parseReportAttributes(Z_attribute_list& attr_list) {
void ZCLFrame::generateSyntheticAttributes(Z_attribute_list& attr_list) {
// scan through attributes and apply specific converters
for (auto &attr : attr_list) {
- if (attr.key_is_str) { continue; } // pass if key is a name
- uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+ if (attr.key_is_str && attr.key_is_cmd) { continue; } // pass if key is a name
+ uint32_t ccccaaaa = (attr.cluster << 16) | attr.attr_id;
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
case 0x0000FF01:
@@ -1296,8 +687,8 @@ void ZCLFrame::generateSyntheticAttributes(Z_attribute_list& attr_list) {
void ZCLFrame::removeInvalidAttributes(Z_attribute_list& attr_list) {
// scan through attributes and apply specific converters
for (auto &attr : attr_list) {
- if (attr.key_is_str) { continue; } // pass if key is a name
- uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+ if (attr.key_is_str && attr.key_is_cmd) { continue; } // pass if key is a name
+ uint32_t ccccaaaa = (attr.cluster << 16) | attr.attr_id;
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
case 0x04020000: // Temperature
@@ -1310,18 +701,54 @@ void ZCLFrame::removeInvalidAttributes(Z_attribute_list& attr_list) {
}
}
+
+//
+// Apply synonyms from the plug-in synonym definitions
+//
+void ZCLFrame::applySynonymAttributes(Z_attribute_list& attr_list) {
+ Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+
+ String modelId((char*) device.modelId);
+ // scan through attributes and apply specific converters
+ for (auto &attr : attr_list) {
+ if (attr.key_is_str) { continue; } // pass if key is a name
+
+ // first apply synonyms from plugins
+ Z_attribute_synonym syn = Z_plugin_matchAttributeSynonym(device.modelId, device.manufacturerId,
+ attr.cluster, attr.attr_id);
+ if (syn.found()) {
+ attr.setKeyId(syn.new_cluster, syn.new_attribute);
+ if ((syn.multiplier != 1 && syn.multiplier != 0) || (syn.divider != 1 && syn.divider != 0) || (syn.base != 0)) {
+ // we need to change the value
+ float fval = attr.getFloat();
+ if (syn.multiplier != 1 && syn.multiplier != 0) {
+ fval = fval * syn.multiplier;
+ }
+ if (syn.divider != 1 && syn.divider != 0) {
+ fval = fval / syn.divider;
+ }
+ if (syn.base != 0) {
+ fval = fval + syn.base;
+ }
+ attr.setFloat(fval);
+ }
+ }
+ }
+}
+
//
// Compute new attributes based on the standard set
// Note: both function are now split to compute on extracted attributes
//
void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
- const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
- const char * model_c = zigbee_devices.getModelId(shortaddr); // null if unknown
- String modelId((char*) model_c);
+ Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+
+ String modelId((char*) device.modelId);
// scan through attributes and apply specific converters
for (auto &attr : attr_list) {
if (attr.key_is_str) { continue; } // pass if key is a name
- uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+
+ uint32_t ccccaaaa = (attr.cluster << 16) | attr.attr_id;
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
case 0x00010020: // BatteryVoltage
@@ -1372,7 +799,7 @@ void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
if (attr_rgb == nullptr) { // make sure we didn't already computed it
uint8_t brightness = 255;
if (device.valid()) {
- const Z_Data_Light & light = device.data.find(_srcendpoint);
+ const Z_Data_Light & light = device.data.find(srcendpoint);
if ((&light != &z_data_unk) && (light.validDimmer())) {
// Dimmer has a valid value
brightness = changeUIntScale(light.getDimmer(), 0, 254, 0, 255); // range is 0..255
@@ -1402,9 +829,29 @@ void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
}
break;
case 0x05000002: // ZoneStatus
- const Z_Data_Alarm & alarm = (const Z_Data_Alarm&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_Alarm, _srcendpoint);
- if (&alarm != nullptr) {
- alarm.convertZoneStatus(attr_list, attr.getUInt());
+ {
+ const Z_Data_Alarm & alarm = (const Z_Data_Alarm&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_Alarm, srcendpoint);
+ if (&alarm != nullptr) {
+ alarm.convertZoneStatus(attr_list, attr.getUInt());
+ }
+ }
+ break;
+ // convert AC multipliers/dividers
+ case 0x0B040600 ... 0x0B040605: // cluser 0x0B04 - attr 0x0600..0x0605
+ {
+ uint16_t val = attr.getUInt();
+ Z_Data_Plug & plug = device.data.get();
+ if (&plug != &z_data_unk) {
+ switch (ccccaaaa) {
+ case 0x0B040600: plug.setACVoltageMul(val); break;
+ case 0x0B040601: plug.setACVoltageDiv(val); break;
+ case 0x0B040602: plug.setACCurrentMul(val); break;
+ case 0x0B040603: plug.setACCurrentDiv(val); break;
+ case 0x0B040604: plug.setACPowerMul(val); break;
+ case 0x0B040605: plug.setACPowerDiv(val); break;
+ }
+ }
+ // AddLog(LOG_LEVEL_INFO, ">>>: cluster=0x%04X attr=0x%04X v=%i", attr.cluster, attr.attr_id, attr.getUInt());
}
break;
}
@@ -1417,20 +864,20 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) {
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
// scan through attributes and apply specific converters
for (auto &attr : attr_list) {
- if (attr.key_is_str) { continue; } // pass if key is a name
- uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+ if (attr.key_is_str && attr.key_is_cmd) { continue; } // pass if key is a name
+ uint32_t ccccaaaa = (attr.cluster << 16) | attr.attr_id;
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
case 0x04060000: // Occupancy
uint32_t occupancy = attr.getUInt();
if (occupancy) {
uint32_t pir_timer = OCCUPANCY_TIMEOUT;
- const Z_Data_PIR & pir_found = (const Z_Data_PIR&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_PIR, _srcendpoint);
+ const Z_Data_PIR & pir_found = (const Z_Data_PIR&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_PIR, srcendpoint);
if (&pir_found != nullptr) {
pir_timer = pir_found.getTimeoutSeconds() * 1000;
}
if (pir_timer > 0) {
- zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, pir_timer, cluster, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
+ zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, pir_timer, cluster, srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
}
} else {
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
@@ -1478,7 +925,7 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
}
// ZCL_READ_ATTRIBUTES
-void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseReadAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t i = 0;
uint32_t len = payload.len();
@@ -1494,15 +941,9 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
read_attr_ids[i/2] = attrid;
// find the attribute name
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
-
- if ((conv_cluster == cluster) && (conv_attribute == attrid)) {
- attr_names.addAttribute(Z_strings + pgm_read_word(&converter->name_offset), true).setBool(true);
- break;
- }
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attrid, false);
+ if (matched_attr.found()) {
+ attr_names.addAttribute(matched_attr.name, true).setBool(true);
}
i += 2;
}
@@ -1516,7 +957,7 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
}
// ZCL_CONFIGURE_REPORTING_RESPONSE
-void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t len = payload.len();
Z_attribute_list attr_config_list;
@@ -1528,8 +969,9 @@ void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
attr_config_response.addAttributePMEM(PSTR("Status")).setUInt(status);
attr_config_response.addAttributePMEM(PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
- const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, nullptr, nullptr);
- if (attr_name) {
+ Z_attribute_match attr_matched = Z_findAttributeMatcherById(shortaddr, cluster, attr_id, false);
+ if (attr_matched.found()) {
+ const __FlashStringHelper* attr_name = (const __FlashStringHelper*) attr_matched.name;
attr_config_list.addAttribute(attr_name).setStrRaw(attr_config_response.toString(true).c_str());
} else {
attr_config_list.addAttribute(cluster, attr_id).setStrRaw(attr_config_response.toString(true).c_str());
@@ -1546,7 +988,7 @@ void ZCLFrame::parseWriteAttributesResponse(Z_attribute_list& attr_list) {
}
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
-void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseReadConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t i = 0;
uint32_t len = payload.len();
@@ -1563,19 +1005,16 @@ void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
attr_2.addAttributePMEM(PSTR("DirectionReceived")).setBool(true);
}
- // find the attribute name
+ // find the multiplier
int8_t multiplier = 1;
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
-
- if ((conv_cluster == cluster) && (conv_attribute == attrid)) {
- const char * attr_name = Z_strings + pgm_read_word(&converter->name_offset);
- attr_2.addAttribute(attr_name, true).setBool(true);
- multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
- break;
- }
+ int8_t divider = 1;
+ int16_t base = 0;
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attrid, false);
+ if (matched_attr.found()) {
+ attr_2.addAttribute(matched_attr.name, true).setBool(true);
+ multiplier = matched_attr.multiplier;
+ divider = matched_attr.divider;
+ base = matched_attr.base;
}
i += 4;
if (0 != status) {
@@ -1601,10 +1040,18 @@ void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
// decode Reportable Change
Z_attribute &attr_change = attr_2.addAttributePMEM(PSTR("ReportableChange"));
i += parseSingleAttribute(attr_change, payload, i, attr_type);
- if ((1 != multiplier) && (0 != multiplier)) {
+
+ if ((multiplier != 1 && multiplier != 0) || (divider != 1 && divider != 0) || (base != 0)) {
float fval = attr_change.getFloat();
- if (multiplier > 0) { fval = fval * multiplier; }
- else { fval = fval / (-multiplier); }
+ if (multiplier != 1 && multiplier != 0) {
+ fval = fval * multiplier;
+ }
+ if (divider != 1 && divider != 0) {
+ fval = fval / divider;
+ }
+ if (base != 0) {
+ fval = fval + base;
+ }
attr_change.setFloat(fval);
}
}
@@ -1653,7 +1100,7 @@ void ZCLFrame::parseResponse_inner(uint8_t cmd, bool cluster_specific, uint8_t s
// "StatusMessage"
attr_list.addAttributePMEM(PSTR(D_JSON_ZIGBEE_STATUS_MSG)).setStr(getZigbeeStatusMessage(status).c_str());
// Add Endpoint
- attr_list.addAttributePMEM(PSTR(D_CMND_ZIGBEE_ENDPOINT)).setUInt(_srcendpoint);
+ attr_list.addAttributePMEM(PSTR(D_CMND_ZIGBEE_ENDPOINT)).setUInt(srcendpoint);
// Add Group if non-zero
if (groupaddr) { // TODO what about group zero
attr_list.group_id = groupaddr;
@@ -1686,32 +1133,41 @@ void Z_ResetDebounce(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, u
void ZCLFrame::parseClusterSpecificCommand(Z_attribute_list& attr_list) {
// Check if debounce is active and if the packet is a duplicate
Z_Device & device = zigbee_devices.getShortAddr(shortaddr);
- if ((device.debounce_endpoint != 0) && (device.debounce_endpoint == _srcendpoint) && (device.debounce_transact == transactseq)) {
+ if ((device.debounce_endpoint != 0) && (device.debounce_endpoint == srcendpoint) && (device.debounce_transact == transactseq)) {
// this is a duplicate, drop the packet
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Discarding duplicate command from 0x%04X, endpoint %d"), shortaddr, _srcendpoint);
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Discarding duplicate command from 0x%04X, endpoint %d"), shortaddr, srcendpoint);
} else {
// reset the duplicate marker, parse the packet normally, and set a timer to reset the marker later (which will discard any existing timer for the same device/endpoint)
- device.debounce_endpoint = _srcendpoint;
+ device.debounce_endpoint = srcendpoint;
device.debounce_transact = transactseq;
- zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, USE_ZIGBEE_DEBOUNCE_COMMANDS, 0 /*clusterid*/, _srcendpoint, Z_CAT_DEBOUNCE_CMD, 0, &Z_ResetDebounce);
+ zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, USE_ZIGBEE_DEBOUNCE_COMMANDS, 0 /*clusterid*/, srcendpoint, Z_CAT_DEBOUNCE_CMD, 0, &Z_ResetDebounce);
- convertClusterSpecific(attr_list, cluster, cmd, _frame_control.b.direction, shortaddr, _srcendpoint, payload);
- if (!Settings->flag5.zb_disable_autoquery) {
- // read attributes unless disabled
- if (!_frame_control.b.direction) { // only handle server->client (i.e. device->coordinator)
- if (_wasbroadcast) { // only update for broadcast messages since we don't see unicast from device to device and we wouldn't know the target
- sendHueUpdate(BAD_SHORTADDR, groupaddr, cluster);
+ bool cmd_parsed = false;
+ if (srcendpoint == 0xF2 && dstendpoint == 0xF2 && cluster == 0x0021) {
+ // handle Green Power commands
+ cmd_parsed = convertGPSpecific(attr_list, cmd, direction, shortaddr, _wasbroadcast, payload);
+ }
+ // was it successfully parsed already?
+ if (!cmd_parsed) {
+ // handle normal commands
+ convertClusterSpecific(attr_list, cluster, cmd, direction, shortaddr, srcendpoint, payload);
+ if (!Settings->flag5.zb_disable_autoquery) {
+ // read attributes unless disabled
+ if (!direction) { // only handle server->client (i.e. device->coordinator)
+ if (_wasbroadcast) { // only update for broadcast messages since we don't see unicast from device to device and we wouldn't know the target
+ sendHueUpdate(BAD_SHORTADDR, groupaddr, cluster);
+ }
}
}
}
}
- // Send Default Response to acknowledge the attribute reporting
+ // Send Default Response to acknowledge the command
if (0 == _frame_control.b.disable_def_resp) {
// the device expects a default response
ZCLFrame zcl(2); // message is 4 bytes
zcl.shortaddr = shortaddr;
zcl.cluster = cluster;
- zcl.dstendpoint = _srcendpoint;
+ zcl.dstendpoint = srcendpoint;
zcl.cmd = ZCL_DEFAULT_RESPONSE;
zcl.manuf = manuf;
zcl.clusterSpecific = false; /* not cluster specific */
@@ -1957,7 +1413,7 @@ void ZCLFrame::syntheticAqaraCubeOrButton(class Z_attribute_list &attr_list, cla
// Aqara vibration device
void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class Z_attribute &attr) {
- switch (attr.key.id.attr_id) {
+ switch (attr.attr_id) {
case 0x0055:
{
int32_t ivalue = attr.getInt();
@@ -2027,50 +1483,37 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
}
// attr is Z_attribute&
- if (!attr.key_is_str) {
- uint16_t cluster = attr.key.id.cluster;
- uint16_t attribute = attr.key.id.attr_id;
- uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
-
+ if (!attr.key_is_str && !attr.key_is_cmd) {
+ uint16_t cluster = attr.cluster;
+ uint16_t attribute = attr.attr_id;
+ uint32_t ccccaaaa = (attr.cluster << 16) | attr.attr_id;
// Look for an entry in the converter table
bool found = false;
- const char * conv_name;
- Z_Data_Type map_type = Z_Data_Type::Z_Unknown;
- uint8_t map_offset = 0;
- uint8_t zigbee_type = Znodata;
- int8_t conv_multiplier;
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
- if ((conv_cluster == cluster) &&
- ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
- conv_multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
- zigbee_type = pgm_read_byte(&converter->type);
- uint8_t mapping = pgm_read_byte(&converter->mapping);
- map_type = (Z_Data_Type) ((mapping & 0xF0)>>4);
- map_offset = (mapping & 0x0F);
- conv_name = Z_strings + pgm_read_word(&converter->name_offset);
- found = true;
- break;
- }
+ // first search in device plug-ins
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attribute, true);
+ found = matched_attr.found();
+ // special case for Tuya attributes, also search for type `FF` if not found
+ if (!found && cluster == 0xEF00) {
+ // search for attribute `FFxx` for wildcard types
+ matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, 0xFF00 | (attribute & 0x00FF), true);
+ found = matched_attr.found();
}
- float fval = attr.getFloat();
- if (found && (map_type != Z_Data_Type::Z_Unknown)) {
+ float fval = attr.getFloat();
+ if (found && (matched_attr.map_type != Z_Data_Type::Z_Unknown)) {
// We apply an automatic mapping to Z_Data_XXX object
- // First we find or instantiate the correct Z_Data_XXX accorfing to the endpoint
+ // First we find or instantiate the correct Z_Data_XXX according to the endpoint
// Then store the attribute at the attribute addres (via offset) and according to size 8/16/32 bits
// add the endpoint if it was not already known
device.addEndpoint(src_ep);
// we don't apply the multiplier, but instead store in Z_Data_XXX object
- Z_Data & data = device.data.getByType(map_type, src_ep);
- uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + map_offset;
+ Z_Data & data = device.data.getByType(matched_attr.map_type, src_ep);
+ uint8_t * attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + matched_attr.map_offset;
uint32_t uval32 = attr.getUInt(); // call converter to uint only once
int32_t ival32 = attr.getInt(); // call converter to int only once
- // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) map_type, map_offset, zigbee_type, ival32);
+ // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) matched_attr.matched_attr, matched_attr.map_offset, matched_attr.zigbee_type, ival32);
switch (ccccaaaa) {
case 0xEF000202:
case 0xEF000203: // need to convert Tuya temperatures from 1/10 to 1/00 °C
@@ -2078,7 +1521,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
break;
}
- switch (zigbee_type) {
+ switch (matched_attr.zigbee_type) {
case Zenum8:
case Zmap8:
case Zbool:
@@ -2120,22 +1563,45 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
break;
case 0x00060000:
case 0x00068000: device.setPower(attr.getBool(), src_ep); break;
+ // apply multiplier/divisor to AC values
+ case 0x0B040505: // RMSVoltage
+ case 0x0B040508: // RMSCurrent
+ case 0x0B04050B: // ActivePower
+ {
+ const Z_Data_Plug & plug = device.data.find();
+ if (&plug != &z_data_unk) {
+ switch (ccccaaaa) {
+ case 0x0B040505: fval = fval * plug.getACVoltageMul() / plug.getACVoltageDiv(); break;
+ case 0x0B040508: fval = fval * plug.getACCurrentMul() / plug.getACCurrentDiv(); break;
+ case 0x0B04050B: fval = fval * plug.getACPowerMul() / plug.getACPowerDiv(); break;
+ }
+ attr.setFloat(fval);
+ }
+ }
+ break;
}
// now apply the multiplier to make it human readable
if (found) {
- if (0 == conv_multiplier) { attr_list.removeAttribute(&attr); continue; } // remove attribute if multiplier is zero
- if (1 != conv_multiplier) {
- if (conv_multiplier > 0) { fval = fval * conv_multiplier; }
- else { fval = fval / (-conv_multiplier); }
+ if (0 == matched_attr.multiplier) { attr_list.removeAttribute(&attr); continue; } // remove attribute if multiplier is zero
+ if ((matched_attr.multiplier != 1 && matched_attr.multiplier != 0) || (matched_attr.divider != 1 && matched_attr.divider != 0) || (matched_attr.base != 0)) {
+ if (matched_attr.multiplier != 1 && matched_attr.multiplier != 0) {
+ fval = fval * matched_attr.multiplier;
+ }
+ if (matched_attr.divider != 1 && matched_attr.divider != 0) {
+ fval = fval / matched_attr.divider;
+ }
+ if (matched_attr.base != 0) {
+ fval = fval + matched_attr.base;
+ }
attr.setFloat(fval);
}
}
// Replace cluster/attribute with name
if (found) {
- if (0x00 != pgm_read_byte(conv_name)) {// if name is not null, replace it
- attr.setKeyName(conv_name, true); // PMEM string so no need to copy
+ if (0x00 != pgm_read_byte(matched_attr.name)) {// if name is not null, replace it
+ attr.setKeyName(matched_attr.name, true); // PMEM string so no need to copy
}
}
}
@@ -2143,35 +1609,28 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
}
// Internal search function
-void Z_parseAttributeKey_inner(class Z_attribute & attr, uint16_t preferred_cluster) {
- // scan attributes to find by name, and retrieve type
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t local_attr_id = pgm_read_word(&converter->attribute);
- uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint8_t local_type_id = pgm_read_byte(&converter->type);
- int8_t local_multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
- // AddLog(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
-
- if (!attr.key_is_str) {
- if ((attr.key.id.cluster == local_cluster_id) && (attr.key.id.attr_id == local_attr_id)) {
- attr.attr_type = local_type_id;
- break;
- }
- } else if (pgm_read_word(&converter->name_offset)) {
- const char * key = attr.key.key;
- // AddLog(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
- if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) {
- if ((preferred_cluster == 0xFFFF) || // any cluster
- (local_cluster_id == preferred_cluster)) {
- // match
- attr.setKeyId(local_cluster_id, local_attr_id);
- attr.attr_type = local_type_id;
- attr.attr_multiplier = local_multiplier;
- break;
- }
+void Z_parseAttributeKey_inner(uint16_t shortaddr, class Z_attribute & attr, uint16_t preferred_cluster) {
+ if (attr.key_is_str) {
+ // find the attribute name
+ Z_attribute_match matched_attr = Z_findAttributeMatcherByName(shortaddr, attr.key);
+ if (matched_attr.found()) {
+ if ((preferred_cluster == 0xFFFF) || // any cluster
+ (matched_attr.cluster == preferred_cluster)) {
+ // match
+ attr.setKeyId(matched_attr.cluster, matched_attr.attribute);
+ attr.attr_type = matched_attr.zigbee_type;
+ attr.attr_multiplier = matched_attr.multiplier;
+ attr.attr_divider = matched_attr.divider;
+ attr.manuf = matched_attr.manuf;
}
}
+ } else {
+ // find by cluster/attribute
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, attr.cluster, attr.attr_id, false);
+ if (matched_attr.found()) {
+ attr.attr_type = matched_attr.zigbee_type;
+ attr.manuf = matched_attr.manuf;
+ }
}
}
@@ -2191,11 +1650,11 @@ void Z_parseAttributeKey_inner(class Z_attribute & attr, uint16_t preferred_clus
// Note: the attribute value is unchanged and unparsed
//
// Note: if the type is specified in the key, the multiplier is not applied, no conversion happens
-bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
+bool Z_parseAttributeKey(uint16_t shortaddr, class Z_attribute & attr, uint16_t preferred_cluster) {
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
if (attr.key_is_str) {
- const char * key = attr.key.key;
+ const char * key = attr.key;
char * delimiter = strchr(key, '/');
char * delimiter2 = strchr(key, '%');
if (delimiter) {
@@ -2208,7 +1667,7 @@ bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
attr_id = strtoul(delimiter+1, nullptr, 16);
} else {
attr_id = strtoul(delimiter+1, &delimiter2, 16);
- type_id = strtoul(delimiter2+1, nullptr, 16);
+ type_id = Z_getTypeByName(delimiter2+1);
}
attr.setKeyId(cluster_id, attr_id);
attr.attr_type = type_id;
@@ -2218,12 +1677,19 @@ bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
// do we already know the type, i.e. attribute and cluster are also known
if ((Zunk == attr.attr_type) && (preferred_cluster != 0xFFFF)) {
- Z_parseAttributeKey_inner(attr, preferred_cluster); // try to find with the selected cluster
+ Z_parseAttributeKey_inner(shortaddr, attr, preferred_cluster); // try to find with the selected cluster
}
if (Zunk == attr.attr_type) {
- Z_parseAttributeKey_inner(attr, 0xFFFF); // try again with any cluster
+ Z_parseAttributeKey_inner(shortaddr, attr, 0xFFFF); // try again with any cluster
}
- return (Zunk != attr.attr_type) ? true : false;
+ // special case for Tuya attributes, where Zunk is allowed
+ if (Zunk == attr.attr_type) {
+ if (!attr.key_is_str && attr.cluster == 0xEF00) {
+ return true;
+ }
+ return false; // couldn't find any match
+ }
+ return true;
}
// generic toAttributes() based on declaration in the attribute array
@@ -2238,7 +1704,11 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list) const {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA;
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
- int8_t multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ int8_t multiplier = 1;
+ int8_t divider = 1;
+ int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ if (multiplier8 > 1) { multiplier = multiplier8; }
+ if (multiplier8 < 0) { divider = -multiplier8; }
Z_Data_Type map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
uint8_t map_offset = (conv_mapping & 0x0F);
@@ -2270,11 +1740,27 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list) const {
float fval;
if (data_size > 0) { fval = uval32; }
else { fval = ival32; }
- if ((1 != multiplier) && (0 != multiplier)) {
- if (multiplier > 0) { fval = fval * multiplier; }
- else { fval = fval / (-multiplier); }
+ if ((multiplier != 1 && multiplier != 0) || (divider != 1 && divider != 0)) {
+ if (multiplier != 1 && multiplier != 0) {
+ fval = fval * multiplier;
+ }
+ if (divider != 1 && divider != 0) {
+ fval = fval / divider;
+ }
+ }
+ // special case for plugs, with parametric multiplier/divisor
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof" // avoid warnings since we're using offsetof() in a risky way
+ const Z_Data_Plug * plug = (Z_Data_Plug*) this;
+ if (map_type == Z_Data_Type::Z_Plug) {
+ if (map_offset == Z_OFFSET(Z_Data_Plug, mains_voltage)) {
+ fval = fval * plug->getACVoltageMul() / plug->getACVoltageDiv();
+ } else if (map_offset == Z_OFFSET(Z_Data_Plug, mains_power)) {
+ fval = fval * plug->getACPowerMul() / plug->getACPowerDiv();
+ }
}
attr.setFloat(fval);
+#pragma GCC diagnostic pop
}
}
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino
deleted file mode 100644
index df2d3c0be..000000000
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino
+++ /dev/null
@@ -1,955 +0,0 @@
-/*
- xdrv_23_zigbee_5__constants.ino - zigbee support for Tasmota
-
- Copyright (C) 2021 Theo Arends and Stephan Hadinger
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#ifdef USE_ZIGBEE
-
-// Below is a compilation of Strings used in Zigbee commands and converters.
-// Instead of using pointer to strings (4 bytes), we are using an offset (16 bits)
-// into an array of strings - which leads to a 1/3 more compact structure.
-
-// To generate the code below use https://tasmota.hadinger.fr/util
-// and copy/paste the entire arrays `Z_PostProcess` and `Z_Commands` concatenated
-// Note: the 'C' syntax is irrelevant, the parser only looks for `Z_()`
-
-// In addition the Python3 code used is below:
-
-/*Python code to generate code below
-
-import re
-pat = r"Z\(([^\)]+)\)" # extract text in Z() macro
-
-def clean(s):
- return s.strip(" \t\n\r")
-
-def strings_to_pmem(arg):
- #strings = arg.split("\n")
- strings = re.findall(pat, arg)
-
- # do some basic cleaning
- strings_cleaned = [ clean(x) for x in strings if clean(x) != ""]
-
- # remove duplicates
- strings_cleaned = list(dict.fromkeys(strings_cleaned))
-
- out_s = "const char Z_strings[] PROGMEM = \n"
- out_i = "enum Z_offsets {\n"
-
- index = 0;
- # add a first empty string
- out_s += " \"\\x00\"\n"
- out_i += " Zo_ = " + str(index) + ",\n"
- index += 1
-
- for s in strings_cleaned:
- out_s += " \"" + s + "\" \"\\x00\"\n"
- out_i += " Zo_" + s + " = " + str(index) + ",\n"
- index += len(s) + 1 # add one for null char
-
- out_s += " \"\\x00\";"
- out_i += "};"
-
- return ("", out_s, out_i)
-
-
-*/
-
-/*
- DO NOT EDIT
-*/
-
-const char Z_strings[] PROGMEM =
- "\x00"
- "00" "\x00"
- "00190200" "\x00"
- "00xx0A00" "\x00"
- "00xxxx000000000000" "\x00"
- "01" "\x00"
- "0101" "\x00"
- "01190200" "\x00"
- "01xx0A00" "\x00"
- "01xxxx" "\x00"
- "01xxxx000000000000" "\x00"
- "01xxxx0A0000000000" "\x00"
- "03xx0A00" "\x00"
- "03xxxx000000000000" "\x00"
- "03xxxx0A0000000000" "\x00"
- "AccelerationTimeLift" "\x00"
- "ActivePower" "\x00"
- "ActuatorEnabled" "\x00"
- "AddGroup" "\x00"
- "AddScene" "\x00"
- "AlarmCount" "\x00"
- "AnalogApplicationType" "\x00"
- "AnalogDescription" "\x00"
- "AnalogEngineeringUnits" "\x00"
- "AnalogInApplicationType" "\x00"
- "AnalogInDescription" "\x00"
- "AnalogInEngineeringUnits" "\x00"
- "AnalogInMaxValue" "\x00"
- "AnalogInMinValue" "\x00"
- "AnalogInOutOfService" "\x00"
- "AnalogInReliability" "\x00"
- "AnalogInResolution" "\x00"
- "AnalogInStatusFlags" "\x00"
- "AnalogOutApplicationType" "\x00"
- "AnalogOutDescription" "\x00"
- "AnalogOutEngineeringUnits" "\x00"
- "AnalogOutMaxValue" "\x00"
- "AnalogOutMinValue" "\x00"
- "AnalogOutOfService" "\x00"
- "AnalogOutOutOfService" "\x00"
- "AnalogOutReliability" "\x00"
- "AnalogOutRelinquishDefault" "\x00"
- "AnalogOutResolution" "\x00"
- "AnalogOutStatusFlags" "\x00"
- "AnalogOutValue" "\x00"
- "AnalogPriorityArray" "\x00"
- "AnalogReliability" "\x00"
- "AnalogRelinquishDefault" "\x00"
- "AnalogStatusFlags" "\x00"
- "AnalogValue" "\x00"
- "AppVersion" "\x00"
- "ApparentPower" "\x00"
- "AqaraAccelerometer" "\x00"
- "AqaraRotate" "\x00"
- "AqaraVibration505" "\x00"
- "AqaraVibrationMode" "\x00"
- "AqaraVibrationsOrAngle" "\x00"
- "Aqara_FF05" "\x00"
- "ArrowClick" "\x00"
- "ArrowHold" "\x00"
- "ArrowRelease" "\x00"
- "AvailablePower" "\x00"
- "BatteryPercentage" "\x00"
- "BatteryVoltage" "\x00"
- "BinaryActiveText" "\x00"
- "BinaryApplicationType" "\x00"
- "BinaryDescription" "\x00"
- "BinaryInActiveText" "\x00"
- "BinaryInApplicationType" "\x00"
- "BinaryInDescription" "\x00"
- "BinaryInInactiveText" "\x00"
- "BinaryInOutOfService" "\x00"
- "BinaryInPolarity" "\x00"
- "BinaryInReliability" "\x00"
- "BinaryInStatusFlags" "\x00"
- "BinaryInValue" "\x00"
- "BinaryInactiveText" "\x00"
- "BinaryMinimumOffTime" "\x00"
- "BinaryMinimumOnTime" "\x00"
- "BinaryOutActiveText" "\x00"
- "BinaryOutApplicationType" "\x00"
- "BinaryOutDescription" "\x00"
- "BinaryOutInactiveText" "\x00"
- "BinaryOutMinimumOffTime" "\x00"
- "BinaryOutMinimumOnTime" "\x00"
- "BinaryOutOfService" "\x00"
- "BinaryOutOutOfService" "\x00"
- "BinaryOutPolarity" "\x00"
- "BinaryOutReliability" "\x00"
- "BinaryOutRelinquishDefault" "\x00"
- "BinaryOutStatusFlags" "\x00"
- "BinaryOutValue" "\x00"
- "BinaryReliability" "\x00"
- "BinaryRelinquishDefault" "\x00"
- "BinaryStatusFlags" "\x00"
- "BinaryValue" "\x00"
- "CIE" "\x00"
- "CO" "\x00"
- "CT" "\x00"
- "CheckinInterval" "\x00"
- "CheckinIntervalMin" "\x00"
- "ClosedLimit" "\x00"
- "Color" "\x00"
- "ColorMode" "\x00"
- "ColorMove" "\x00"
- "ColorPointBIntensity" "\x00"
- "ColorPointBX" "\x00"
- "ColorPointBY" "\x00"
- "ColorPointGIntensity" "\x00"
- "ColorPointGX" "\x00"
- "ColorPointGY" "\x00"
- "ColorPointRIntensity" "\x00"
- "ColorPointRX" "\x00"
- "ColorPointRY" "\x00"
- "ColorStep" "\x00"
- "ColorTempMove" "\x00"
- "ColorTempMoveDown" "\x00"
- "ColorTempMoveStop" "\x00"
- "ColorTempMoveUp" "\x00"
- "ColorTempStep" "\x00"
- "ColorTempStepDown" "\x00"
- "ColorTempStepUp" "\x00"
- "CompanyName" "\x00"
- "CompensationText" "\x00"
- "ConfigStatus" "\x00"
- "Contact" "\x00"
- "ControlSequenceOfOperation" "\x00"
- "CurrentGroup" "\x00"
- "CurrentPositionLift" "\x00"
- "CurrentPositionLiftPercentage" "\x00"
- "CurrentPositionTilt" "\x00"
- "CurrentPositionTiltPercentage" "\x00"
- "CurrentScene" "\x00"
- "CurrentTemperature" "\x00"
- "CurrentTemperatureSetPoint" "\x00"
- "CustomerName" "\x00"
- "DataQualityID" "\x00"
- "DateCode" "\x00"
- "DecelerationTimeLift" "\x00"
- "Dimmer" "\x00"
- "DimmerDown" "\x00"
- "DimmerMove" "\x00"
- "DimmerOptions" "\x00"
- "DimmerRemainingTime" "\x00"
- "DimmerStep" "\x00"
- "DimmerStepDown" "\x00"
- "DimmerStepUp" "\x00"
- "DimmerStop" "\x00"
- "DimmerUp" "\x00"
- "DoorClosedEvents" "\x00"
- "DoorOpenEvents" "\x00"
- "DoorState" "\x00"
- "DriftCompensation" "\x00"
- "DstEnd" "\x00"
- "DstShift" "\x00"
- "DstStart" "\x00"
- "EnergyFormatting" "\x00"
- "EnergyRemote" "\x00"
- "EnergyTotal" "\x00"
- "EurotronicErrors" "\x00"
- "EurotronicHostFlags" "\x00"
- "FastPollTimeout" "\x00"
- "FastPollTimeoutMax" "\x00"
- "Fire" "\x00"
- "FlowMaxMeasuredValue" "\x00"
- "FlowMinMeasuredValue" "\x00"
- "FlowRate" "\x00"
- "FlowTolerance" "\x00"
- "GenericDeviceClass" "\x00"
- "GenericDeviceType" "\x00"
- "GetAllGroups" "\x00"
- "GetGroup" "\x00"
- "GetSceneMembership" "\x00"
- "GlassBreak" "\x00"
- "GroupNameSupport" "\x00"
- "HWVersion" "\x00"
- "Hue" "\x00"
- "HueMove" "\x00"
- "HueSat" "\x00"
- "HueStep" "\x00"
- "HueStepDown" "\x00"
- "HueStepUp" "\x00"
- "Humidity" "\x00"
- "HumidityMaxMeasuredValue" "\x00"
- "HumidityMinMeasuredValue" "\x00"
- "HumidityTolerance" "\x00"
- "Identify" "\x00"
- "IdentifyQuery" "\x00"
- "IdentifyTime" "\x00"
- "Illuminance" "\x00"
- "IlluminanceLevelStatus" "\x00"
- "IlluminanceLightSensorType" "\x00"
- "IlluminanceMaxMeasuredValue" "\x00"
- "IlluminanceMinMeasuredValue" "\x00"
- "IlluminanceTargetLevel" "\x00"
- "IlluminanceTolerance" "\x00"
- "InstalledClosedLimitLift" "\x00"
- "InstalledClosedLimitTilt" "\x00"
- "InstalledOpenLimitLift" "\x00"
- "InstalledOpenLimitTilt" "\x00"
- "IntermediateSetpointsLift" "\x00"
- "IntermediateSetpointsTilt" "\x00"
- "LastMessageLQI" "\x00"
- "LastMessageRSSI" "\x00"
- "LastSetTime" "\x00"
- "LegrandHeatingMode" "\x00"
- "LegrandMode" "\x00"
- "LegrandOpt1" "\x00"
- "LegrandOpt2" "\x00"
- "LegrandOpt3" "\x00"
- "LidlPower" "\x00"
- "LocalTemperature" "\x00"
- "LocalTemperatureCalibration" "\x00"
- "LocalTime" "\x00"
- "LocationAge" "\x00"
- "LocationMethod" "\x00"
- "LocationType" "\x00"
- "LockState" "\x00"
- "LockType" "\x00"
- "LongPollInterval" "\x00"
- "LongPollIntervalMin" "\x00"
- "MainsFrequency" "\x00"
- "MainsVoltage" "\x00"
- "Manufacturer" "\x00"
- "MaxTempExperienced" "\x00"
- "MeterTypeID" "\x00"
- "MinTempExperienced" "\x00"
- "Mode" "\x00"
- "Model" "\x00"
- "ModelId" "\x00"
- "MotorStepSize" "\x00"
- "Movement" "\x00"
- "MullerLightMode" "\x00"
- "MultiApplicationType" "\x00"
- "MultiDescription" "\x00"
- "MultiInApplicationType" "\x00"
- "MultiInDescription" "\x00"
- "MultiInNumberOfStates" "\x00"
- "MultiInOutOfService" "\x00"
- "MultiInReliability" "\x00"
- "MultiInStatusFlags" "\x00"
- "MultiInValue" "\x00"
- "MultiNumberOfStates" "\x00"
- "MultiOutApplicationType" "\x00"
- "MultiOutDescription" "\x00"
- "MultiOutNumberOfStates" "\x00"
- "MultiOutOfService" "\x00"
- "MultiOutOutOfService" "\x00"
- "MultiOutReliability" "\x00"
- "MultiOutRelinquishDefault" "\x00"
- "MultiOutStatusFlags" "\x00"
- "MultiOutValue" "\x00"
- "MultiReliability" "\x00"
- "MultiRelinquishDefault" "\x00"
- "MultiStatusFlags" "\x00"
- "MultiValue" "\x00"
- "MultipleScheduling" "\x00"
- "NumberOfDevices" "\x00"
- "NumberOfPrimaries" "\x00"
- "NumberOfResets" "\x00"
- "NumberofActuationsLift" "\x00"
- "NumberofActuationsTilt" "\x00"
- "Occupancy" "\x00"
- "OccupancySensorType" "\x00"
- "OccupiedCoolingSetpoint" "\x00"
- "OccupiedHeatingSetpoint" "\x00"
- "OnOffTransitionTime" "\x00"
- "OpenPeriod" "\x00"
- "OppleMode" "\x00"
- "OutdoorTemperature" "\x00"
- "OverTempTotalDwell" "\x00"
- "PICoolingDemand" "\x00"
- "PIHeatingDemand" "\x00"
- "POD" "\x00"
- "Panic" "\x00"
- "PartNumber" "\x00"
- "PersistentMemoryWrites" "\x00"
- "PersonalAlarm" "\x00"
- "PhysicalClosedLimit" "\x00"
- "PhysicalClosedLimitLift" "\x00"
- "PhysicalClosedLimitTilt" "\x00"
- "Power" "\x00"
- "Power2" "\x00"
- "Power3" "\x00"
- "Power4" "\x00"
- "PowerOffEffect" "\x00"
- "PowerOnRecall" "\x00"
- "PowerOnTimer" "\x00"
- "PowerSource" "\x00"
- "PowerThreshold" "\x00"
- "Pressure" "\x00"
- "PressureMaxMeasuredValue" "\x00"
- "PressureMaxScaledValue" "\x00"
- "PressureMinMeasuredValue" "\x00"
- "PressureMinScaledValue" "\x00"
- "PressureScale" "\x00"
- "PressureScaledTolerance" "\x00"
- "PressureScaledValue" "\x00"
- "PressureTolerance" "\x00"
- "Primary1Intensity" "\x00"
- "Primary1X" "\x00"
- "Primary1Y" "\x00"
- "Primary2Intensity" "\x00"
- "Primary2X" "\x00"
- "Primary2Y" "\x00"
- "Primary3Intensity" "\x00"
- "Primary3X" "\x00"
- "Primary3Y" "\x00"
- "ProductCode" "\x00"
- "ProductRevision" "\x00"
- "ProductURL" "\x00"
- "QualityMeasure" "\x00"
- "RGB" "\x00"
- "RMSCurrent" "\x00"
- "RMSVoltage" "\x00"
- "ReactivePower" "\x00"
- "RecallScene" "\x00"
- "RemainingTime" "\x00"
- "RemoteSensing" "\x00"
- "RemoveAllGroups" "\x00"
- "RemoveAllScenes" "\x00"
- "RemoveGroup" "\x00"
- "RemoveScene" "\x00"
- "ResetAlarm" "\x00"
- "ResetAllAlarms" "\x00"
- "SWBuildID" "\x00"
- "Sat" "\x00"
- "SatMove" "\x00"
- "SatStep" "\x00"
- "SceneCount" "\x00"
- "SceneValid" "\x00"
- "ScheduleMode" "\x00"
- "SeaPressure" "\x00"
- "ShortPollInterval" "\x00"
- "Shutter" "\x00"
- "ShutterClose" "\x00"
- "ShutterLift" "\x00"
- "ShutterOpen" "\x00"
- "ShutterStop" "\x00"
- "ShutterTilt" "\x00"
- "SoftwareRevision" "\x00"
- "StackVersion" "\x00"
- "StandardTime" "\x00"
- "StartUpOnOff" "\x00"
- "Status" "\x00"
- "StoreScene" "\x00"
- "SwitchType" "\x00"
- "SystemMode" "\x00"
- "TRVBoost" "\x00"
- "TRVChildProtection" "\x00"
- "TRVMirrorDisplay" "\x00"
- "TRVMode" "\x00"
- "TRVWindowOpen" "\x00"
- "TempTarget" "\x00"
- "Temperature" "\x00"
- "TemperatureMaxMeasuredValue" "\x00"
- "TemperatureMinMeasuredValue" "\x00"
- "TemperatureTolerance" "\x00"
- "TerncyDuration" "\x00"
- "TerncyRotate" "\x00"
- "ThSetpoint" "\x00"
- "Time" "\x00"
- "TimeEpoch" "\x00"
- "TimeStatus" "\x00"
- "TimeZone" "\x00"
- "TotalProfileNum" "\x00"
- "TuyaAutoLock" "\x00"
- "TuyaAwayDays" "\x00"
- "TuyaAwayTemp" "\x00"
- "TuyaBattery" "\x00"
- "TuyaBoostTime" "\x00"
- "TuyaCalibration" "\x00"
- "TuyaCalibrationTime" "\x00"
- "TuyaChildLock" "\x00"
- "TuyaComfortTemp" "\x00"
- "TuyaEcoTemp" "\x00"
- "TuyaFanMode" "\x00"
- "TuyaForceMode" "\x00"
- "TuyaMCUVersion" "\x00"
- "TuyaMaxTemp" "\x00"
- "TuyaMinTemp" "\x00"
- "TuyaMotorReversal" "\x00"
- "TuyaMovingState" "\x00"
- "TuyaPreset" "\x00"
- "TuyaQuery" "\x00"
- "TuyaScheduleHolidays" "\x00"
- "TuyaScheduleWorkdays" "\x00"
- "TuyaTempTarget" "\x00"
- "TuyaValveDetection" "\x00"
- "TuyaValvePosition" "\x00"
- "TuyaWeekSelect" "\x00"
- "TuyaWindowDetection" "\x00"
- "UnoccupiedCoolingSetpoint" "\x00"
- "UnoccupiedHeatingSetpoint" "\x00"
- "UtilityName" "\x00"
- "ValidUntilTime" "\x00"
- "ValvePosition" "\x00"
- "VelocityLift" "\x00"
- "ViewGroup" "\x00"
- "ViewScene" "\x00"
- "Water" "\x00"
- "WhitePointX" "\x00"
- "WhitePointY" "\x00"
- "WindowCoveringType" "\x00"
- "X" "\x00"
- "Y" "\x00"
- "ZCLVersion" "\x00"
- "ZoneState" "\x00"
- "ZoneStatus" "\x00"
- "ZoneStatusChange" "\x00"
- "ZoneType" "\x00"
- "_" "\x00"
- "xx" "\x00"
- "xx000A00" "\x00"
- "xx0A" "\x00"
- "xx0A00" "\x00"
- "xx19" "\x00"
- "xx190A" "\x00"
- "xx190A00" "\x00"
- "xxxx" "\x00"
- "xxxx00" "\x00"
- "xxxx0A00" "\x00"
- "xxxxyy" "\x00"
- "xxxxyyyy" "\x00"
- "xxxxyyyy0A00" "\x00"
- "xxxxyyzz" "\x00"
- "xxyy" "\x00"
- "xxyy0A00" "\x00"
- "xxyyyy" "\x00"
- "xxyyyy000000000000" "\x00"
- "xxyyyy0A0000000000" "\x00"
- "xxyyyyzz" "\x00"
- "xxyyyyzzzz" "\x00"
- "xxyyzzzz" "\x00"
- ;
-enum Z_offsets {
- Zo_ = 0,
- Zo_00 = 1,
- Zo_00190200 = 4,
- Zo_00xx0A00 = 13,
- Zo_00xxxx000000000000 = 22,
- Zo_01 = 41,
- Zo_0101 = 44,
- Zo_01190200 = 49,
- Zo_01xx0A00 = 58,
- Zo_01xxxx = 67,
- Zo_01xxxx000000000000 = 74,
- Zo_01xxxx0A0000000000 = 93,
- Zo_03xx0A00 = 112,
- Zo_03xxxx000000000000 = 121,
- Zo_03xxxx0A0000000000 = 140,
- Zo_AccelerationTimeLift = 159,
- Zo_ActivePower = 180,
- Zo_ActuatorEnabled = 192,
- Zo_AddGroup = 208,
- Zo_AddScene = 217,
- Zo_AlarmCount = 226,
- Zo_AnalogApplicationType = 237,
- Zo_AnalogDescription = 259,
- Zo_AnalogEngineeringUnits = 277,
- Zo_AnalogInApplicationType = 300,
- Zo_AnalogInDescription = 324,
- Zo_AnalogInEngineeringUnits = 344,
- Zo_AnalogInMaxValue = 369,
- Zo_AnalogInMinValue = 386,
- Zo_AnalogInOutOfService = 403,
- Zo_AnalogInReliability = 424,
- Zo_AnalogInResolution = 444,
- Zo_AnalogInStatusFlags = 463,
- Zo_AnalogOutApplicationType = 483,
- Zo_AnalogOutDescription = 508,
- Zo_AnalogOutEngineeringUnits = 529,
- Zo_AnalogOutMaxValue = 555,
- Zo_AnalogOutMinValue = 573,
- Zo_AnalogOutOfService = 591,
- Zo_AnalogOutOutOfService = 610,
- Zo_AnalogOutReliability = 632,
- Zo_AnalogOutRelinquishDefault = 653,
- Zo_AnalogOutResolution = 680,
- Zo_AnalogOutStatusFlags = 700,
- Zo_AnalogOutValue = 721,
- Zo_AnalogPriorityArray = 736,
- Zo_AnalogReliability = 756,
- Zo_AnalogRelinquishDefault = 774,
- Zo_AnalogStatusFlags = 798,
- Zo_AnalogValue = 816,
- Zo_AppVersion = 828,
- Zo_ApparentPower = 839,
- Zo_AqaraAccelerometer = 853,
- Zo_AqaraRotate = 872,
- Zo_AqaraVibration505 = 884,
- Zo_AqaraVibrationMode = 902,
- Zo_AqaraVibrationsOrAngle = 921,
- Zo_Aqara_FF05 = 944,
- Zo_ArrowClick = 955,
- Zo_ArrowHold = 966,
- Zo_ArrowRelease = 976,
- Zo_AvailablePower = 989,
- Zo_BatteryPercentage = 1004,
- Zo_BatteryVoltage = 1022,
- Zo_BinaryActiveText = 1037,
- Zo_BinaryApplicationType = 1054,
- Zo_BinaryDescription = 1076,
- Zo_BinaryInActiveText = 1094,
- Zo_BinaryInApplicationType = 1113,
- Zo_BinaryInDescription = 1137,
- Zo_BinaryInInactiveText = 1157,
- Zo_BinaryInOutOfService = 1178,
- Zo_BinaryInPolarity = 1199,
- Zo_BinaryInReliability = 1216,
- Zo_BinaryInStatusFlags = 1236,
- Zo_BinaryInValue = 1256,
- Zo_BinaryInactiveText = 1270,
- Zo_BinaryMinimumOffTime = 1289,
- Zo_BinaryMinimumOnTime = 1310,
- Zo_BinaryOutActiveText = 1330,
- Zo_BinaryOutApplicationType = 1350,
- Zo_BinaryOutDescription = 1375,
- Zo_BinaryOutInactiveText = 1396,
- Zo_BinaryOutMinimumOffTime = 1418,
- Zo_BinaryOutMinimumOnTime = 1442,
- Zo_BinaryOutOfService = 1465,
- Zo_BinaryOutOutOfService = 1484,
- Zo_BinaryOutPolarity = 1506,
- Zo_BinaryOutReliability = 1524,
- Zo_BinaryOutRelinquishDefault = 1545,
- Zo_BinaryOutStatusFlags = 1572,
- Zo_BinaryOutValue = 1593,
- Zo_BinaryReliability = 1608,
- Zo_BinaryRelinquishDefault = 1626,
- Zo_BinaryStatusFlags = 1650,
- Zo_BinaryValue = 1668,
- Zo_CIE = 1680,
- Zo_CO = 1684,
- Zo_CT = 1687,
- Zo_CheckinInterval = 1690,
- Zo_CheckinIntervalMin = 1706,
- Zo_ClosedLimit = 1725,
- Zo_Color = 1737,
- Zo_ColorMode = 1743,
- Zo_ColorMove = 1753,
- Zo_ColorPointBIntensity = 1763,
- Zo_ColorPointBX = 1784,
- Zo_ColorPointBY = 1797,
- Zo_ColorPointGIntensity = 1810,
- Zo_ColorPointGX = 1831,
- Zo_ColorPointGY = 1844,
- Zo_ColorPointRIntensity = 1857,
- Zo_ColorPointRX = 1878,
- Zo_ColorPointRY = 1891,
- Zo_ColorStep = 1904,
- Zo_ColorTempMove = 1914,
- Zo_ColorTempMoveDown = 1928,
- Zo_ColorTempMoveStop = 1946,
- Zo_ColorTempMoveUp = 1964,
- Zo_ColorTempStep = 1980,
- Zo_ColorTempStepDown = 1994,
- Zo_ColorTempStepUp = 2012,
- Zo_CompanyName = 2028,
- Zo_CompensationText = 2040,
- Zo_ConfigStatus = 2057,
- Zo_Contact = 2070,
- Zo_ControlSequenceOfOperation = 2078,
- Zo_CurrentGroup = 2105,
- Zo_CurrentPositionLift = 2118,
- Zo_CurrentPositionLiftPercentage = 2138,
- Zo_CurrentPositionTilt = 2168,
- Zo_CurrentPositionTiltPercentage = 2188,
- Zo_CurrentScene = 2218,
- Zo_CurrentTemperature = 2231,
- Zo_CurrentTemperatureSetPoint = 2250,
- Zo_CustomerName = 2277,
- Zo_DataQualityID = 2290,
- Zo_DateCode = 2304,
- Zo_DecelerationTimeLift = 2313,
- Zo_Dimmer = 2334,
- Zo_DimmerDown = 2341,
- Zo_DimmerMove = 2352,
- Zo_DimmerOptions = 2363,
- Zo_DimmerRemainingTime = 2377,
- Zo_DimmerStep = 2397,
- Zo_DimmerStepDown = 2408,
- Zo_DimmerStepUp = 2423,
- Zo_DimmerStop = 2436,
- Zo_DimmerUp = 2447,
- Zo_DoorClosedEvents = 2456,
- Zo_DoorOpenEvents = 2473,
- Zo_DoorState = 2488,
- Zo_DriftCompensation = 2498,
- Zo_DstEnd = 2516,
- Zo_DstShift = 2523,
- Zo_DstStart = 2532,
- Zo_EnergyFormatting = 2541,
- Zo_EnergyRemote = 2558,
- Zo_EnergyTotal = 2571,
- Zo_EurotronicErrors = 2583,
- Zo_EurotronicHostFlags = 2600,
- Zo_FastPollTimeout = 2620,
- Zo_FastPollTimeoutMax = 2636,
- Zo_Fire = 2655,
- Zo_FlowMaxMeasuredValue = 2660,
- Zo_FlowMinMeasuredValue = 2681,
- Zo_FlowRate = 2702,
- Zo_FlowTolerance = 2711,
- Zo_GenericDeviceClass = 2725,
- Zo_GenericDeviceType = 2744,
- Zo_GetAllGroups = 2762,
- Zo_GetGroup = 2775,
- Zo_GetSceneMembership = 2784,
- Zo_GlassBreak = 2803,
- Zo_GroupNameSupport = 2814,
- Zo_HWVersion = 2831,
- Zo_Hue = 2841,
- Zo_HueMove = 2845,
- Zo_HueSat = 2853,
- Zo_HueStep = 2860,
- Zo_HueStepDown = 2868,
- Zo_HueStepUp = 2880,
- Zo_Humidity = 2890,
- Zo_HumidityMaxMeasuredValue = 2899,
- Zo_HumidityMinMeasuredValue = 2924,
- Zo_HumidityTolerance = 2949,
- Zo_Identify = 2967,
- Zo_IdentifyQuery = 2976,
- Zo_IdentifyTime = 2990,
- Zo_Illuminance = 3003,
- Zo_IlluminanceLevelStatus = 3015,
- Zo_IlluminanceLightSensorType = 3038,
- Zo_IlluminanceMaxMeasuredValue = 3065,
- Zo_IlluminanceMinMeasuredValue = 3093,
- Zo_IlluminanceTargetLevel = 3121,
- Zo_IlluminanceTolerance = 3144,
- Zo_InstalledClosedLimitLift = 3165,
- Zo_InstalledClosedLimitTilt = 3190,
- Zo_InstalledOpenLimitLift = 3215,
- Zo_InstalledOpenLimitTilt = 3238,
- Zo_IntermediateSetpointsLift = 3261,
- Zo_IntermediateSetpointsTilt = 3287,
- Zo_LastMessageLQI = 3313,
- Zo_LastMessageRSSI = 3328,
- Zo_LastSetTime = 3344,
- Zo_LegrandHeatingMode = 3356,
- Zo_LegrandMode = 3375,
- Zo_LegrandOpt1 = 3387,
- Zo_LegrandOpt2 = 3399,
- Zo_LegrandOpt3 = 3411,
- Zo_LidlPower = 3423,
- Zo_LocalTemperature = 3433,
- Zo_LocalTemperatureCalibration = 3450,
- Zo_LocalTime = 3478,
- Zo_LocationAge = 3488,
- Zo_LocationMethod = 3500,
- Zo_LocationType = 3515,
- Zo_LockState = 3528,
- Zo_LockType = 3538,
- Zo_LongPollInterval = 3547,
- Zo_LongPollIntervalMin = 3564,
- Zo_MainsFrequency = 3584,
- Zo_MainsVoltage = 3599,
- Zo_Manufacturer = 3612,
- Zo_MaxTempExperienced = 3625,
- Zo_MeterTypeID = 3644,
- Zo_MinTempExperienced = 3656,
- Zo_Mode = 3675,
- Zo_Model = 3680,
- Zo_ModelId = 3686,
- Zo_MotorStepSize = 3694,
- Zo_Movement = 3708,
- Zo_MullerLightMode = 3717,
- Zo_MultiApplicationType = 3733,
- Zo_MultiDescription = 3754,
- Zo_MultiInApplicationType = 3771,
- Zo_MultiInDescription = 3794,
- Zo_MultiInNumberOfStates = 3813,
- Zo_MultiInOutOfService = 3835,
- Zo_MultiInReliability = 3855,
- Zo_MultiInStatusFlags = 3874,
- Zo_MultiInValue = 3893,
- Zo_MultiNumberOfStates = 3906,
- Zo_MultiOutApplicationType = 3926,
- Zo_MultiOutDescription = 3950,
- Zo_MultiOutNumberOfStates = 3970,
- Zo_MultiOutOfService = 3993,
- Zo_MultiOutOutOfService = 4011,
- Zo_MultiOutReliability = 4032,
- Zo_MultiOutRelinquishDefault = 4052,
- Zo_MultiOutStatusFlags = 4078,
- Zo_MultiOutValue = 4098,
- Zo_MultiReliability = 4112,
- Zo_MultiRelinquishDefault = 4129,
- Zo_MultiStatusFlags = 4152,
- Zo_MultiValue = 4169,
- Zo_MultipleScheduling = 4180,
- Zo_NumberOfDevices = 4199,
- Zo_NumberOfPrimaries = 4215,
- Zo_NumberOfResets = 4233,
- Zo_NumberofActuationsLift = 4248,
- Zo_NumberofActuationsTilt = 4271,
- Zo_Occupancy = 4294,
- Zo_OccupancySensorType = 4304,
- Zo_OccupiedCoolingSetpoint = 4324,
- Zo_OccupiedHeatingSetpoint = 4348,
- Zo_OnOffTransitionTime = 4372,
- Zo_OpenPeriod = 4392,
- Zo_OppleMode = 4403,
- Zo_OutdoorTemperature = 4413,
- Zo_OverTempTotalDwell = 4432,
- Zo_PICoolingDemand = 4451,
- Zo_PIHeatingDemand = 4467,
- Zo_POD = 4483,
- Zo_Panic = 4487,
- Zo_PartNumber = 4493,
- Zo_PersistentMemoryWrites = 4504,
- Zo_PersonalAlarm = 4527,
- Zo_PhysicalClosedLimit = 4541,
- Zo_PhysicalClosedLimitLift = 4561,
- Zo_PhysicalClosedLimitTilt = 4585,
- Zo_Power = 4609,
- Zo_Power2 = 4615,
- Zo_Power3 = 4622,
- Zo_Power4 = 4629,
- Zo_PowerOffEffect = 4636,
- Zo_PowerOnRecall = 4651,
- Zo_PowerOnTimer = 4665,
- Zo_PowerSource = 4678,
- Zo_PowerThreshold = 4690,
- Zo_Pressure = 4705,
- Zo_PressureMaxMeasuredValue = 4714,
- Zo_PressureMaxScaledValue = 4739,
- Zo_PressureMinMeasuredValue = 4762,
- Zo_PressureMinScaledValue = 4787,
- Zo_PressureScale = 4810,
- Zo_PressureScaledTolerance = 4824,
- Zo_PressureScaledValue = 4848,
- Zo_PressureTolerance = 4868,
- Zo_Primary1Intensity = 4886,
- Zo_Primary1X = 4904,
- Zo_Primary1Y = 4914,
- Zo_Primary2Intensity = 4924,
- Zo_Primary2X = 4942,
- Zo_Primary2Y = 4952,
- Zo_Primary3Intensity = 4962,
- Zo_Primary3X = 4980,
- Zo_Primary3Y = 4990,
- Zo_ProductCode = 5000,
- Zo_ProductRevision = 5012,
- Zo_ProductURL = 5028,
- Zo_QualityMeasure = 5039,
- Zo_RGB = 5054,
- Zo_RMSCurrent = 5058,
- Zo_RMSVoltage = 5069,
- Zo_ReactivePower = 5080,
- Zo_RecallScene = 5094,
- Zo_RemainingTime = 5106,
- Zo_RemoteSensing = 5120,
- Zo_RemoveAllGroups = 5134,
- Zo_RemoveAllScenes = 5150,
- Zo_RemoveGroup = 5166,
- Zo_RemoveScene = 5178,
- Zo_ResetAlarm = 5190,
- Zo_ResetAllAlarms = 5201,
- Zo_SWBuildID = 5216,
- Zo_Sat = 5226,
- Zo_SatMove = 5230,
- Zo_SatStep = 5238,
- Zo_SceneCount = 5246,
- Zo_SceneValid = 5257,
- Zo_ScheduleMode = 5268,
- Zo_SeaPressure = 5281,
- Zo_ShortPollInterval = 5293,
- Zo_Shutter = 5311,
- Zo_ShutterClose = 5319,
- Zo_ShutterLift = 5332,
- Zo_ShutterOpen = 5344,
- Zo_ShutterStop = 5356,
- Zo_ShutterTilt = 5368,
- Zo_SoftwareRevision = 5380,
- Zo_StackVersion = 5397,
- Zo_StandardTime = 5410,
- Zo_StartUpOnOff = 5423,
- Zo_Status = 5436,
- Zo_StoreScene = 5443,
- Zo_SwitchType = 5454,
- Zo_SystemMode = 5465,
- Zo_TRVBoost = 5476,
- Zo_TRVChildProtection = 5485,
- Zo_TRVMirrorDisplay = 5504,
- Zo_TRVMode = 5521,
- Zo_TRVWindowOpen = 5529,
- Zo_TempTarget = 5543,
- Zo_Temperature = 5554,
- Zo_TemperatureMaxMeasuredValue = 5566,
- Zo_TemperatureMinMeasuredValue = 5594,
- Zo_TemperatureTolerance = 5622,
- Zo_TerncyDuration = 5643,
- Zo_TerncyRotate = 5658,
- Zo_ThSetpoint = 5671,
- Zo_Time = 5682,
- Zo_TimeEpoch = 5687,
- Zo_TimeStatus = 5697,
- Zo_TimeZone = 5708,
- Zo_TotalProfileNum = 5717,
- Zo_TuyaAutoLock = 5733,
- Zo_TuyaAwayDays = 5746,
- Zo_TuyaAwayTemp = 5759,
- Zo_TuyaBattery = 5772,
- Zo_TuyaBoostTime = 5784,
- Zo_TuyaCalibration = 5798,
- Zo_TuyaCalibrationTime = 5814,
- Zo_TuyaChildLock = 5834,
- Zo_TuyaComfortTemp = 5848,
- Zo_TuyaEcoTemp = 5864,
- Zo_TuyaFanMode = 5876,
- Zo_TuyaForceMode = 5888,
- Zo_TuyaMCUVersion = 5902,
- Zo_TuyaMaxTemp = 5917,
- Zo_TuyaMinTemp = 5929,
- Zo_TuyaMotorReversal = 5941,
- Zo_TuyaMovingState = 5959,
- Zo_TuyaPreset = 5975,
- Zo_TuyaQuery = 5986,
- Zo_TuyaScheduleHolidays = 5996,
- Zo_TuyaScheduleWorkdays = 6017,
- Zo_TuyaTempTarget = 6038,
- Zo_TuyaValveDetection = 6053,
- Zo_TuyaValvePosition = 6072,
- Zo_TuyaWeekSelect = 6090,
- Zo_TuyaWindowDetection = 6105,
- Zo_UnoccupiedCoolingSetpoint = 6125,
- Zo_UnoccupiedHeatingSetpoint = 6151,
- Zo_UtilityName = 6177,
- Zo_ValidUntilTime = 6189,
- Zo_ValvePosition = 6204,
- Zo_VelocityLift = 6218,
- Zo_ViewGroup = 6231,
- Zo_ViewScene = 6241,
- Zo_Water = 6251,
- Zo_WhitePointX = 6257,
- Zo_WhitePointY = 6269,
- Zo_WindowCoveringType = 6281,
- Zo_X = 6300,
- Zo_Y = 6302,
- Zo_ZCLVersion = 6304,
- Zo_ZoneState = 6315,
- Zo_ZoneStatus = 6325,
- Zo_ZoneStatusChange = 6336,
- Zo_ZoneType = 6353,
- Zo__ = 6362,
- Zo_xx = 6364,
- Zo_xx000A00 = 6367,
- Zo_xx0A = 6376,
- Zo_xx0A00 = 6381,
- Zo_xx19 = 6388,
- Zo_xx190A = 6393,
- Zo_xx190A00 = 6400,
- Zo_xxxx = 6409,
- Zo_xxxx00 = 6414,
- Zo_xxxx0A00 = 6421,
- Zo_xxxxyy = 6430,
- Zo_xxxxyyyy = 6437,
- Zo_xxxxyyyy0A00 = 6446,
- Zo_xxxxyyzz = 6459,
- Zo_xxyy = 6468,
- Zo_xxyy0A00 = 6473,
- Zo_xxyyyy = 6482,
- Zo_xxyyyy000000000000 = 6489,
- Zo_xxyyyy0A0000000000 = 6508,
- Zo_xxyyyyzz = 6527,
- Zo_xxyyyyzzzz = 6536,
- Zo_xxyyzzzz = 6547,
-};
-
-
-/*
- DO NOT EDIT
-*/
-
-
-#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_0_commands.ino
similarity index 87%
rename from tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino
rename to tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_0_commands.ino
index f951e09e9..4aaad582b 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_0_commands.ino
@@ -145,6 +145,60 @@ const Z_CommandConverter Z_Commands[] PROGMEM = {
{ Z_(), 0xEF00, 0xFF, 0x83, Z_() }, // capture any command in 0xEF00 cluster
// Terncy specific
{ Z_(), 0xFCCC, 0x00, 0x82, Z_(xxyy) }, // Terncy button (multi-)press
+ // Green Power - for display only
+ { Z_(GPIdentify), 0xF021, 0x00, 0x01, Z_() },
+ { Z_(GPScene0), 0xF021, 0x10, 0x01, Z_() },
+ { Z_(GPScene1), 0xF021, 0x11, 0x01, Z_() },
+ { Z_(GPScene2), 0xF021, 0x12, 0x01, Z_() },
+ { Z_(GPScene3), 0xF021, 0x13, 0x01, Z_() },
+ { Z_(GPScene4), 0xF021, 0x14, 0x01, Z_() },
+ { Z_(GPScene5), 0xF021, 0x15, 0x01, Z_() },
+ { Z_(GPScene6), 0xF021, 0x16, 0x01, Z_() },
+ { Z_(GPScene7), 0xF021, 0x17, 0x01, Z_() },
+ { Z_(GPScene8), 0xF021, 0x18, 0x01, Z_() },
+ { Z_(GPScene9), 0xF021, 0x19, 0x01, Z_() },
+ { Z_(GPScene10), 0xF021, 0x1A, 0x01, Z_() },
+ { Z_(GPScene11), 0xF021, 0x1B, 0x01, Z_() },
+ { Z_(GPScene12), 0xF021, 0x1C, 0x01, Z_() },
+ { Z_(GPScene13), 0xF021, 0x1D, 0x01, Z_() },
+ { Z_(GPScene14), 0xF021, 0x1E, 0x01, Z_() },
+ { Z_(GPScene15), 0xF021, 0x1F, 0x01, Z_() },
+ { Z_(GPOff), 0xF021, 0x20, 0x01, Z_() },
+ { Z_(GPOn), 0xF021, 0x21, 0x01, Z_() },
+ { Z_(GPToggle), 0xF021, 0x22, 0x01, Z_() },
+ { Z_(GPRelease), 0xF021, 0x23, 0x01, Z_() },
+ { Z_(GPMoveUp), 0xF021, 0x30, 0x01, Z_(xx) },
+ { Z_(GPMoveDown), 0xF021, 0x31, 0x01, Z_(xx) },
+ { Z_(GPStepUp), 0xF021, 0x32, 0x01, Z_(xx) },
+ { Z_(GPStepDown), 0xF021, 0x33, 0x01, Z_(xx) },
+ { Z_(GPLevelStop), 0xF021, 0x34, 0x01, Z_() },
+ { Z_(GPMoveUpOnOff), 0xF021, 0x35, 0x01, Z_(xx) },
+ { Z_(GPMoveDownOnOff),0xF021, 0x36, 0x01, Z_(xx) },
+ { Z_(GPStepUpOnOff), 0xF021, 0x37, 0x01, Z_(xx) },
+ { Z_(GPStepDownOnOff),0xF021, 0x38, 0x01, Z_(xx) },
+ { Z_(GPHueStop), 0xF021, 0x40, 0x01, Z_() },
+ { Z_(GPMoveHueUp), 0xF021, 0x41, 0x01, Z_(xx) },
+ { Z_(GPMoveHueDown), 0xF021, 0x42, 0x01, Z_(xx) },
+ { Z_(GPStepHueUp), 0xF021, 0x43, 0x01, Z_(xx) },
+ { Z_(GPStepHueDown), 0xF021, 0x44, 0x01, Z_(xx) },
+ { Z_(GPSatStop), 0xF021, 0x45, 0x01, Z_() },
+ { Z_(GPMoveSatUp), 0xF021, 0x46, 0x01, Z_(xx) },
+ { Z_(GPMoveSatDown), 0xF021, 0x47, 0x01, Z_(xx) },
+ { Z_(GPStepSatUp), 0xF021, 0x48, 0x01, Z_(xx) },
+ { Z_(GPStepSatDown), 0xF021, 0x49, 0x01, Z_(xx) },
+ { Z_(GPMoveColor), 0xF021, 0x4A, 0x01, Z_(xxxxyyyy) },
+ { Z_(GPStepColor), 0xF021, 0x4B, 0x01, Z_(xxxxyyyy) },
+ { Z_(GPLockDoor), 0xF021, 0x50, 0x01, Z_() },
+ { Z_(GPUnlockDoor), 0xF021, 0x51, 0x01, Z_() },
+ { Z_(GPPress1of1), 0xF021, 0x60, 0x01, Z_() },
+ { Z_(GPRelease1of1), 0xF021, 0x61, 0x01, Z_() },
+ { Z_(GPPress1of2), 0xF021, 0x62, 0x01, Z_() },
+ { Z_(GPRelease1of2), 0xF021, 0x63, 0x01, Z_() },
+ { Z_(GPPress2of2), 0xF021, 0x64, 0x01, Z_() },
+ { Z_(GPRelease2of2), 0xF021, 0x65, 0x01, Z_() },
+ { Z_(GPShortPress1of1),0xF021, 0x66, 0x01, Z_() },
+ { Z_(GPShortPress1of2),0xF021, 0x67, 0x01, Z_() },
+ { Z_(GPShortPress2of2),0xF021, 0x68, 0x01, Z_() },
};
/*********************************************************************************************\
@@ -212,7 +266,6 @@ void Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
zigbee_devices.getShortAddr(shortaddr).setReachable(false); // mark device as reachable
Z_attribute_list attr_list;
attr_list.addAttributePMEM(PSTR("Reachable")).setBool(false); // "Reachable":false
- // Z_postProcessAttributes(shortaddr, endpoint, attr_list); // make sure all is updated accordingly
zigbee_devices.jsonPublishNow(shortaddr, attr_list);
}
}
@@ -284,13 +337,31 @@ void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz)
}
}
-
// Parse a cluster specific command, and try to convert into human readable
+// Includes specific handling for Tuya attributes
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
const char * command_name = nullptr;
uint8_t conv_direction;
Z_XYZ_Var xyz;
+ // always report attribute in raw format
+ // Format: "0001!06": "00" = "!": "" for commands to devices
+ // Format: "0004<00": "00" = "<": "" for commands to devices
+ // char attrid_str[12];
+ // snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '?' : '!', cmd);
+ // Z_attribute & attr_raw = attr_list.addAttribute(attrid_str);
+ Z_attribute & attr_raw = attr_list.addAttributeCmd(cluster, cmd, direction, false /* cluster specific */);
+ attr_raw.setBuf(payload, 0, payload.len());
+
+ // Take a shotcut in case of Tuya attribute which follow a different scheme
+ if (cluster == 0xEF00) {
+ // Tuya Cmd
+ if (convertTuyaSpecificCluster(attr_list, cluster, cmd, direction, shortaddr, srcendpoint, payload)) {
+ attr_list.removeAttribute(&attr_raw); // remove raw command
+ }
+ return; // abort, normal processing doesn't apply here
+ }
+
//AddLog(LOG_LEVEL_INFO, PSTR(">>> len = %d - %02X%02X%02X"), payload.len(), payload.get8(0), payload.get8(1), payload.get8(2));
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i];
@@ -344,13 +415,7 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
}
}
- // always report attribute in raw format
- // Format: "0001!06": "00" = "!": "" for commands to devices
- // Format: "0004<00": "00" = "<": "" for commands to devices
- char attrid_str[12];
- snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
- Z_attribute & attr_raw = attr_list.addAttribute(attrid_str);
- attr_raw.setBuf(payload, 0, payload.len());
+ // TODO Berry encode command
if (command_name) {
// Now try to transform into a human readable format
@@ -515,22 +580,21 @@ void parseSingleTuyaAttribute(Z_attribute & attr, const SBuffer &buf,
//
// Tuya - MOES specifc cluster 0xEF00
-// See https://medium.com/@dzegarra/zigbee2mqtt-how-to-add-support-for-a-new-tuya-based-device-part-2-5492707e882d
-// and https://github.com/Koenkk/zigbee-herdsman-converters/blob/9f503d47d3df6a99d133b78d2b52aa5c701ddddf/converters/fromZigbee.js#L339
+// https://developer.tuya.com/en/docs/iot-device-dev/tuya-zigbee-universal-docking-access-standard?id=K9ik6zvofpzql#subtitle-6-Private%20cluster
//
bool convertTuyaSpecificCluster(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &buf) {
- // uint8_t status = buf.get8(0);
- // uint8_t transid = buf.get8(1);
- uint8_t dp = buf.get8(2); // dpid from Tuya documentation
+ // uint16_t seq_number = buf.get16BigEndian(0)
+ uint8_t dpid = buf.get8(2); // dpid from Tuya documentation
uint8_t attr_type = buf.get8(3); // data type from Tuya documentation
uint16_t len = buf.get16BigEndian(4);
if ((1 == cmd) || (2 == cmd)) { // attribute report or attribute response
- // create a synthetic attribute with id 'dp'
- Z_attribute & attr = attr_list.addAttribute(cluster, (attr_type << 8) | dp);
+ // create a synthetic attribute with id 'dpid'
+ Z_attribute & attr = attr_list.addAttribute(cluster, (attr_type << 8) | dpid);
parseSingleTuyaAttribute(attr, buf, 6, len, attr_type);
return true; // true = remove the original Tuya attribute
}
+ // TODO Cmd 0x24 to sync clock with coordinator time
return false;
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_1_greenpower.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_1_greenpower.ino
new file mode 100644
index 000000000..24a11dec6
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_1_greenpower.ino
@@ -0,0 +1,336 @@
+/*
+ xdrv_23_zigbee_8_1_greenpower.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+bool convertGPDF_data(class Z_attribute_list &attr_list, uint16_t shortaddr, uint8_t gpcmd, bool wasbroadcast, const SBuffer &payload, size_t payload_start, size_t payload_len) {
+ bool parsed = false;
+ // register the raw command in synthetic cluster 0xF021 corresponding to the GPDF command
+ SBuffer gppayload = payload.subBuffer(payload_start, payload_len);
+ convertClusterSpecific(attr_list, 0xF021, gpcmd, false, shortaddr, 0xF2, gppayload);
+
+ uint16_t cluster = 0xFFFF;
+ uint8_t cmd = 0xFF;
+ SBuffer arg(8);
+ // arguments pre-munched
+ uint8_t arg8 = (payload_len > 0) ? payload.get8(payload_start) : 0xFF;
+ uint16_t arg16 = (payload_len > 1) ? payload.get16(payload_start) : 0xFFFF;
+ uint16_t arg16_1 = (payload_len > 2) ? payload.get16(payload_start + 1) : 0xFFFF; // moved by 1 bytes
+ uint32_t arg32 = (payload_len > 3) ? payload.get32(payload_start) : 0xFFFFFFFF;
+ switch (gpcmd) {
+ case 0x00: cluster = 0x0003; cmd = 0x00; arg.add16(0x003C); break; // Identify - Identify - 0x003C
+ case 0x10 ... 0x1F: cluster = 0x0005; cmd = 0x01; arg.add8(gpcmd - 0x10); break; // Scenes - View Scene
+ case 0x20: cluster = 0x0006; cmd = 0x00; break; // OnOff - Off
+ case 0x21: cluster = 0x0006; cmd = 0x01; break; // OnOff - On
+ case 0x22: cluster = 0x0006; cmd = 0x02; break; // OnOff - Toggle
+ case 0x23: break; // Release
+ case 0x30: cluster = 0x0008; cmd = 0x01; arg.add8(0x00); arg.add8(arg8); break; // Level Control - Move - Up, rate=arg8
+ case 0x31: cluster = 0x0008; cmd = 0x01; arg.add8(0x01); arg.add8(arg8); break; // Level Control - Move - Down, rate=arg8
+ case 0x32: cluster = 0x0008; cmd = 0x02; arg.add8(0x00); arg.add8(arg8); arg.add16(arg16); break; // Level Control - Step - Up, step size=arg8, rate=arg16 (opt)
+ case 0x33: cluster = 0x0008; cmd = 0x02; arg.add8(0x01); arg.add8(arg8); arg.add16(arg16); break; // Level Control - Step - Down, step size=arg8, rate=arg16 (opt)
+ case 0x34: cluster = 0x0008; cmd = 0x03; break; // Level Control - Stop
+ case 0x35: cluster = 0x0008; cmd = 0x05; arg.add8(0x00); arg.add8(arg8); break; // Level Control - Move - Up, rate=arg8
+ case 0x36: cluster = 0x0008; cmd = 0x05; arg.add8(0x01); arg.add8(arg8); break; // Level Control - Move - Down, rate=arg8
+ case 0x37: cluster = 0x0008; cmd = 0x06; arg.add8(0x00); arg.add8(arg8); arg.add16(arg16_1); break; // Level Control - Step - Up, step size=arg8, rate=arg16 (opt)
+ case 0x38: cluster = 0x0008; cmd = 0x06; arg.add8(0x01); arg.add8(arg8); arg.add16(arg16_1); break; // Level Control - Step - Down, step size=arg8, rate=arg16 (opt)
+ case 0x40: cluster = 0x0300; cmd = 0x01; arg.add16(0x1900); break; // Color Control - Move Hue - Stop 25 steps
+ case 0x41: cluster = 0x0300; cmd = 0x01; arg.add8(0x01); arg.add8(arg8); break; // Color Control - Move Hue - Up, rate=arg8
+ case 0x42: cluster = 0x0300; cmd = 0x01; arg.add8(0x03); arg.add8(arg8); break; // Color Control - Move Hue - Down, rate=arg8
+ case 0x43: cluster = 0x0300; cmd = 0x02; arg.add8(0x01); arg.add8(arg8); arg.add8(arg16_1); break; // Color Control - Step Hue - Up - step size/rate
+ case 0x44: cluster = 0x0300; cmd = 0x02; arg.add8(0x03); arg.add8(arg8); arg.add8(arg16_1); break; // Color Control - Step Hue - Down - step size/rate
+ case 0x45: cluster = 0x0300; cmd = 0x04; arg.add16(0x1900); break; // Color Control - Move Sat - Stop 25 steps
+ case 0x46: cluster = 0x0300; cmd = 0x04; arg.add8(0x01); arg.add8(arg8); break; // Color Control - Move Sat - Up, rate=arg8
+ case 0x47: cluster = 0x0300; cmd = 0x04; arg.add8(0x03); arg.add8(arg8); break; // Color Control - Move Sat - Down, rate=arg8
+ case 0x48: cluster = 0x0300; cmd = 0x05; arg.add8(0x01); arg.add8(arg8); arg.add8(arg16_1); break; // Color Control - Step Sat - Up - step size/rate
+ case 0x49: cluster = 0x0300; cmd = 0x05; arg.add8(0x03); arg.add8(arg8); arg.add8(arg16_1); break; // Color Control - Step Sat - Down - step size/rate
+ case 0x4A: cluster = 0x0300; cmd = 0x08; arg.add32(arg32); break; // Color Control - Move Color - RateX/RateY
+ case 0x4B: cluster = 0x0300; cmd = 0x09; arg.addBuffer(payload); break; // Color Control - Step Color - copy all
+ case 0x50: cluster = 0x0101; cmd = 0x00; break; // Door Lock - Lock Door
+ case 0x51: cluster = 0x0101; cmd = 0x01; break; // Door Lock - Unlock Door
+ default: break;
+ }
+
+ if (cluster != 0xFFFF) {
+ convertClusterSpecific(attr_list, cluster, cmd, false, shortaddr, 0xF2, arg);
+ parsed = true;
+ }
+ return parsed;
+}
+
+
+// Handle GPDF Commissioning 0xE0 command
+bool convertGPDF_Commissioning(class Z_attribute_list &attr_list, uint16_t shortaddr, bool wasbroadcast, const SBuffer &payload, size_t payload_start, size_t payload_len) {
+ uint32_t idx_offset = payload_start; // offset compared to minimal packet
+ uint8_t gpd_device_id = payload.get8(idx_offset++); // type of device
+
+ uint8_t gpd_options = payload.get8(idx_offset++);
+ bool gpd_mac_seq_number = (gpd_options & 0x01);
+ bool gpd_rx_on = (gpd_options & 0x02); // is the device capable of receiving data (generally not)
+ bool gpd_app_info_present = (gpd_options & 0x04); // app info extensions at the end of the packet
+ bool gpd_panid_request = (gpd_options & 0x10);
+ bool gpd_sec_key_request = (gpd_options & 0x20);
+ bool gpd_fixed_location = (gpd_options & 0x40);
+ bool gpd_options_has_ext = (gpd_options & 0x80);
+
+ uint8_t gpd_options_ext = 0;
+ if (gpd_options_has_ext) {
+ gpd_options_ext = payload.get8(idx_offset++);
+ }
+ uint8_t gpd_sec_level_capa = (gpd_options_ext & 0x03);
+ uint8_t gpd_key_type = (gpd_options_ext >> 2) & 0x07;
+ uint8_t gpd_key_present = (gpd_options_ext & 0x20);
+ uint8_t gpd_key_encryption = (gpd_options_ext & 0x40);
+ uint8_t gpd_out_counter_present = (gpd_options_ext & 0x80);
+
+ uint64_t gpd_key_low = 0;
+ uint64_t gpd_key_high = 0;
+ if (gpd_key_present) {
+ gpd_key_low = payload.get64(idx_offset);
+ gpd_key_high = payload.get64(idx_offset + 8);
+ idx_offset += 16;
+ }
+ uint32_t gpd_key_mic = 0;
+ if (gpd_key_present && gpd_key_encryption) {
+ gpd_key_mic = payload.get32(idx_offset);
+ idx_offset += 4;
+ }
+ uint32_t gpd_out_counter = 0;
+ if (gpd_out_counter_present) {
+ gpd_out_counter = payload.get32(idx_offset);
+ idx_offset += 4;
+ }
+
+ Response_P(PSTR("{\"ZbGPDFCommissioningReceived\":{"
+ "\"srcaddr\":\"0x%04X\"," "\"wasbroadcast\":%d,"
+ "\"deviceid\":\"0x%02X\"," "\"options\":\"0x%02X\""),
+ shortaddr, wasbroadcast,
+ gpd_device_id, gpd_options
+ );
+
+ if (gpd_options_has_ext) {
+ ResponseAppend_P(PSTR(",\"optionsext\":\"0x%02X\""), gpd_options_ext);
+ }
+ if (gpd_key_present) {
+ ResponseAppend_P(PSTR(",\"securitykey\":\"0x%_X%_X\""), &gpd_key_high, &gpd_key_low);
+ }
+ if (gpd_key_present && gpd_key_encryption) {
+ ResponseAppend_P(PSTR(",\"keymic\":\"0x%08X\""), gpd_key_mic);
+ }
+ if (gpd_out_counter_present) {
+ ResponseAppend_P(PSTR(",\"outcounter\":\"0x%08X\""), gpd_out_counter);
+ }
+
+ // App info extension for Commissioning
+ if (gpd_app_info_present && idx_offset < payload_len + payload_start) {
+ uint8_t gp_app_info = payload.get8(idx_offset++);
+ bool gp_app_manuf_present = (gp_app_info & 0x01);
+ bool gp_app_modelid_present = (gp_app_info & 0x02);
+ bool gp_app_cmd_list_present = (gp_app_info & 0x04);
+ bool gp_app_cluster_reports_present = (gp_app_info & 0x08);
+
+ if (gp_app_manuf_present) {
+ uint16_t gp_app_manuf_id = payload.get8(idx_offset);
+ idx_offset += 2;
+ ResponseAppend_P(PSTR(",\"manufid\":\"0x%04X\""), gp_app_manuf_id);
+ }
+
+ if (gp_app_modelid_present) {
+ uint16_t gp_app_model_id = payload.get8(idx_offset);
+ idx_offset += 2;
+ ResponseAppend_P(PSTR(",\"modelid\":\"0x%04X\""), gp_app_model_id);
+ }
+
+ if (gp_app_cmd_list_present) {
+ uint8_t gpid_len = payload.get8(idx_offset++); // number of entries
+ JsonGeneratorArray gpdi_list;
+ for (uint32_t i = 0; i < gpid_len; i++) {
+ if (idx_offset >= payload_len + payload_start) { break; } // end of payload
+ gpdi_list.add(payload.get8(idx_offset++));
+ }
+ ResponseAppend_P(PSTR(",\"commandid\":%s"), gpdi_list.toString().c_str());
+ }
+
+ if (gp_app_cluster_reports_present) {
+ uint8_t clust_report_len = payload.get8(idx_offset++); // number of entries
+ JsonGeneratorArray gpdi_list;
+ for (uint32_t i = 0; i < clust_report_len; i++) {
+ if (idx_offset >= payload_len + payload_start) { break; } // end of payload
+ gpdi_list.add(payload.get16(idx_offset));
+ idx_offset += 2;
+ }
+ ResponseAppend_P(PSTR(",\"clusterreports\":%s"), gpdi_list.toString().c_str());
+ }
+ }
+
+ ResponseAppend_P(PSTR("}}"));
+
+ if (Settings->flag3.tuya_serial_mqtt_publish) {
+ MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
+ } else {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), ResponseData());
+ }
+ // TODO publish to Berry
+
+ return true;
+}
+
+// Handle GPDF Channel Request 0xE3 command
+bool convertGPDF_ChannelRequest(class Z_attribute_list &attr_list, uint16_t shortaddr, bool wasbroadcast, const SBuffer &payload, size_t payload_start, size_t payload_len) {
+ uint32_t idx_offset = payload_start; // offset compared to minimal packet
+ uint8_t gpd_channel_toggling = payload.get8(idx_offset++);
+
+ uint8_t gpd_next_channel = gpd_channel_toggling & 0x0F;
+ uint8_t gpd_second_next_channel = (gpd_channel_toggling >> 4) & 0x0F;
+
+ Response_P(PSTR("{\"ZbGPDFChannelRequest\":{"
+ "\"nextchannel\":%i," "\"secondnextchannel\":%i,"
+ "}}"),
+ 11 + gpd_next_channel, 11 + gpd_second_next_channel
+ );
+
+ if (Settings->flag3.tuya_serial_mqtt_publish) {
+ MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
+ } else {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), ResponseData());
+ }
+
+ return true;
+}
+
+// Parse a GPDF (Green Power Data Frame) tunnelled by a Green Power Proxy
+//
+// Returns `true` if parsed successfully
+bool convertGPDF(class Z_attribute_list &attr_list, uint16_t shortaddr, uint8_t gpcmd, bool wasbroadcast, const SBuffer &payload, size_t payload_start, size_t payload_len) {
+ // dispatch depending on the GP command
+ switch (gpcmd) {
+ case 0x00:
+ case 0x10 ... 0x6F:
+ return convertGPDF_data(attr_list, shortaddr, gpcmd, wasbroadcast, payload, payload_start, payload_len);
+ case ZGP_COMMISSIONING:
+ return convertGPDF_Commissioning(attr_list, shortaddr, wasbroadcast, payload, payload_start, payload_len);
+ case ZGP_CHANNEL_REQUEST:
+ return convertGPDF_ChannelRequest(attr_list, shortaddr, wasbroadcast, payload, payload_start, payload_len);
+ }
+ return false;
+}
+
+bool convertGPCommissioningNotification(class Z_attribute_list &attr_list, uint16_t shortaddr, bool wasbroadcast, const SBuffer &payload) {
+ uint32_t idx_offset = 0; // offset due to address format: 0 if 4 bytes, 4 if 8 bytes
+
+ uint16_t gp_options = payload.get16(0);
+ uint8_t gp_app_id = gp_options & 0x07;
+ bool gp_rx_after_tx = (gp_options & 0x08);
+ uint8_t gp_security_level = (gp_options >> 4) & 0x03;
+ uint8_t gp_security_type = (gp_options >> 6) & 0x07;
+ bool gp_security_failed = (gp_options >> 9) & 0x01;
+ bool gp_bidir_cap = (gp_options >> 10) & 0x01;
+ bool gp_proxy_info_present = (gp_options >> 11) & 0x01;
+
+
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_APP_ID (7<<0)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_RX_AFTER_TX (1<<3)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_SECUR_LEVEL (3<<4)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_SECUR_KEY_TYPE (7<<6)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_SECUR_FAILED (1<<9)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_BIDIR_CAP (1<<10)
+// #define ZBEE_ZCL_GP_COMMISSIONING_NOTIFICATION_OPTION_PROXY_INFO_PRESENT (1<<11)
+
+ uint64_t gp_pgd_id;
+ if (gp_app_id == 0x02) {
+ idx_offset = 4;
+ gp_pgd_id = payload.get64(2);
+ } else if (gp_app_id == 0x00) {
+ gp_pgd_id = payload.get32(2);
+ } else {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Invalild GP app_id=%i"), gp_app_id);
+ return false;
+ }
+ uint32_t gp_frame_counter = payload.get32(idx_offset + 6);
+ uint8_t gp_cmd = payload.get8(idx_offset + 10);
+
+ uint16_t gpp_shortaddr = 0xFFFF; // shortaddress of the proxy
+ uint8_t gpp_distance = 0xFF; // actual LQI from the GP device to the proxy
+
+ uint32_t gp_payload_start = idx_offset + 12;
+ uint32_t gp_payload_len = payload.get8(gp_payload_start - 1);
+
+ idx_offset = gp_payload_start + gp_payload_len; // reset to after payload
+ if (gp_proxy_info_present) {
+ gpp_shortaddr = payload.get16(idx_offset);
+ gpp_distance = payload.get8(idx_offset + 2);
+ idx_offset += 3;
+ }
+ uint32_t gp_mic = 0x00000000;
+ if (gp_security_failed) {
+ gp_mic = payload.get32(idx_offset);
+ idx_offset += 4;
+ }
+
+ Response_P(PSTR("{\"" D_JSON_ZIGBEEGP_RECEIVED "\":{"
+ "\"srcaddr\":\"0x%04X\"," "\"wasbroadcast\":%d," "\"zclcmd\":\"GP_COMMISSIONING_NOTIFICATION\","
+ "\"gpoptions\":\"0x%04X\","
+ "\"appid\":%i," "\"rxaftertx\":%i," "\"seclevel\":%i," "\"sectype\":%i," "\"secfailed\":%i,"
+ "\"bidircap\":%i," "\"proxyinfo\":%i,"
+ "\"gpsrcid\":\"0x%_X\"," "\"gpfrmcounter\":\"0x%04X\","
+ "\"gpproxy\":\"0x%04X\"," "\"gpplqi\":%i," "\"gpmic\":\"0x%08X\","
+ "\"gpcmd\":\"0x%02X\","
+ "\"gppayload\":\"%*_H\"}}"),
+ shortaddr, wasbroadcast,
+ gp_options,
+ gp_app_id, gp_rx_after_tx, gp_security_level, gp_security_type, gp_security_failed,
+ gp_bidir_cap, gp_proxy_info_present,
+ &gp_pgd_id, gp_frame_counter,
+ gpp_shortaddr, gpp_distance, gp_mic,
+ gp_cmd,
+ gp_payload_len, payload.buf(gp_payload_start)
+ );
+
+ if (Settings->flag3.tuya_serial_mqtt_publish) {
+ MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
+ } else {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), ResponseData());
+ }
+
+ return convertGPDF(attr_list, shortaddr, gp_cmd, wasbroadcast, payload, gp_payload_start, gp_payload_len);
+}
+
+
+
+// Parse Green Power specific commands
+// Returns: true if parsed successfully, false if revenrt to generix parsing
+bool convertGPSpecific(class Z_attribute_list &attr_list, uint8_t cmd, bool direction, uint16_t shortaddr, bool wasbroadcast, const SBuffer &payload) {
+ if (!direction) {
+ // server commands
+ switch (cmd) {
+ case ZGP_COMMISSIONING_NOTIFICATION:
+ return convertGPCommissioningNotification(attr_list, shortaddr, wasbroadcast, payload);
+ break;
+ default:
+ return false;
+ }
+ } else {
+ // client commands
+ switch (cmd) {
+ default:
+ return false;
+ }
+ }
+}
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino
index 1dfe71f49..58baf9dce 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino
@@ -23,7 +23,7 @@
// Add global functions for Hue Emulation
// idx: index in the list of zigbee_devices
-void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, String *response) {
+void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t ep, uint8_t local_light_subtype, String *response) {
static const char HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE[] PROGMEM =
"%s\"alert\":\"none\","
"\"effect\":\"none\","
@@ -41,7 +41,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri
uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
- const Z_Data_Light & light = device.data.find();
+ const Z_Data_Light & light = device.data.find(ep);
if (&light != &z_data_unk) {
bri = light.getDimmer();
colormode = light.getColorMode();
@@ -51,7 +51,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri
x = light.getX();
y = light.getY();
}
- power = device.getPower();
+ power = device.getPower(ep);
reachable = device.getReachable();
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
@@ -89,29 +89,45 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri
free(buf);
}
-void HueLightStatus2Zigbee(uint16_t shortaddr, String *response)
+void HueLightStatus2Zigbee(uint16_t shortaddr, uint8_t ep, String *response)
{
const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
const char * friendlyName = device.friendlyName;
const char * modelId = device.modelId;
const char * manufacturerId = device.manufacturerId;
- char shortaddrname[8];
- snprintf_P(shortaddrname, sizeof(shortaddrname), PSTR("0x%04X"), shortaddr);
- char* buf = HueLightStatus2Generic((friendlyName) ? friendlyName : shortaddrname,
+ char name[32+4];
+ const char * local_friendfly_name = device.ep_names.getEPName(ep != 0 ? ep : device.endpoints[0]); // try endpoint, if `0` then found nothing
+ if (local_friendfly_name != nullptr) {
+ snprintf_P(name, sizeof(name), PSTR("%s"), local_friendfly_name);
+ } else if (friendlyName != nullptr) {
+ if (ep == 0 || ep == device.endpoints[0]) { // default endpoint, no suffix
+ snprintf_P(name, sizeof(name), PSTR("%s"), friendlyName);
+ } else { // no endpoint specific name so add suffix
+ snprintf_P(name, sizeof(name), PSTR("%s-%i"), friendlyName, ep);
+ }
+ } else {
+ if (ep == 0 || ep == device.endpoints[0]) { // default endpoint, no suffix
+ snprintf_P(name, sizeof(name), PSTR("0x%04X"), shortaddr);
+ } else {
+ snprintf_P(name, sizeof(name), PSTR("0x%04X-%i"), shortaddr, ep);
+ }
+ }
+
+ char* buf = HueLightStatus2Generic(name,
(modelId) ? modelId : PSTR("Unknown"),
(manufacturerId) ? manufacturerId : PSTR("Tasmota"),
- GetHueDeviceId(shortaddr).c_str());
+ GetHueDeviceId(shortaddr, ep).c_str());
*response += buf;
free(buf);
}
-int32_t ZigbeeHueStatus(String * response, uint16_t shortaddr) {
- int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
+int32_t ZigbeeHueStatus(String * response, uint16_t shortaddr, uint8_t ep) {
+ int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr, ep);
if (bulbtype >= 0) { // respond only if eligible
*response += F("{\"state\":");
- HueLightStatus1Zigbee(shortaddr, zigbee_devices.getHueBulbtype(shortaddr), response);
- HueLightStatus2Zigbee(shortaddr, response);
+ HueLightStatus1Zigbee(shortaddr, ep, zigbee_devices.getHueBulbtype(shortaddr, ep), response);
+ HueLightStatus2Zigbee(shortaddr, ep, response);
return 200;
} else {
return -3;
@@ -120,40 +136,51 @@ int32_t ZigbeeHueStatus(String * response, uint16_t shortaddr) {
void ZigbeeCheckHue(String & response, bool * appending) {
uint32_t zigbee_num = zigbee_devices.devicesSize();
- for (uint32_t i = 0; i < zigbee_num; i++) {
- uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr;
- int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
-
- if (bulbtype >= 0) {
- // this bulb is advertized
- if (*appending) { response += ","; }
- response += "\"";
- response += EncodeLightIdZigbee(0, shortaddr);
- response += F("\":{\"state\":");
- HueLightStatus1Zigbee(shortaddr, bulbtype, &response); // TODO
- HueLightStatus2Zigbee(shortaddr, &response);
- *appending = true;
+ for (uint32_t idx = 0; idx < zigbee_num; idx++) {
+ const Z_Device & device = zigbee_devices.devicesAt(idx);
+ uint16_t shortaddr = device.shortaddr;
+
+ for (uint32_t i = 0; i < endpoints_max; i++) {
+ uint8_t ep = device.endpoints[i];
+ if (i > 0 && ep == 0) { break; }
+ int8_t bulbtype = device.getHueBulbtype(ep);
+ if (bulbtype >= 0) {
+ // this bulb is advertized
+ if (*appending) { response += ","; }
+ response += "\"";
+ response += EncodeLightIdZigbee((i == 0) ? 0 : ep, shortaddr);
+ response += F("\":{\"state\":");
+ HueLightStatus1Zigbee(shortaddr, ep, bulbtype, &response);
+ HueLightStatus2Zigbee(shortaddr, (i == 0) ? 0 : ep, &response); // if first endpoint ,announce as `0`
+ *appending = true;
+ }
}
}
}
void ZigbeeHueGroups(String * lights) {
uint32_t zigbee_num = zigbee_devices.devicesSize();
- for (uint32_t i = 0; i < zigbee_num; i++) {
- uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr;
- int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
+ for (uint32_t idx = 0; idx < zigbee_num; idx++) {
+ const Z_Device & device = zigbee_devices.devicesAt(idx);
+ uint16_t shortaddr = device.shortaddr;
- if (bulbtype >= 0) {
- *lights += ",\"";
- *lights += EncodeLightIdZigbee(0, shortaddr);
- *lights += "\"";
+ for (uint32_t i = 0; i < endpoints_max; i++) {
+ uint8_t ep = device.endpoints[i];
+ if (i > 0 && ep == 0) { break; } // no more endpoints
+ int8_t bulbtype = device.getHueBulbtype(ep);
+ if (bulbtype >= 0) {
+ *lights += ",\"";
+ *lights += EncodeLightIdZigbee((i == 0) ? 0 : ep, shortaddr); // if first endpont, announce as `0`
+ *lights += "\"";
+ }
}
}
}
-void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) {
+void ZigbeeSendHue(uint16_t shortaddr, uint8_t ep, uint16_t cluster, uint8_t cmd, const SBuffer & s) {
ZCLFrame zcl(s.len());
zcl.shortaddr = shortaddr;
+ zcl.dstendpoint = ep; // if set to `0`, we will use the first endpoint
zcl.cluster = cluster;
zcl.cmd = cmd;
zcl.clusterSpecific = true;
@@ -165,69 +192,69 @@ void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuf
// Send commands
// Power On/Off
-void ZigbeeHuePower(uint16_t shortaddr, bool power) {
+void ZigbeeHuePower(uint16_t shortaddr, uint8_t ep, bool power) {
SBuffer s(0);
- ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, s);
- zigbee_devices.getShortAddr(shortaddr).setPower(power, 0);
+ ZigbeeSendHue(shortaddr, ep, 0x0006, power ? 1 : 0, s);
+ zigbee_devices.getShortAddr(shortaddr).setPower(power, ep);
}
// Dimmer
-void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) {
+void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t ep, uint8_t dimmer) {
if (dimmer > 0xFE) { dimmer = 0xFE; }
SBuffer s(4);
s.add8(dimmer);
s.add16(0x000A); // transition time = 1s
- ZigbeeSendHue(shortaddr, 0x0008, 0x04, s);
- zigbee_devices.getLight(shortaddr).setDimmer(dimmer);
+ ZigbeeSendHue(shortaddr, ep, 0x0008, 0x04, s);
+ zigbee_devices.getLight(shortaddr, ep).setDimmer(dimmer);
}
// CT
-void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) {
+void ZigbeeHueCT(uint16_t shortaddr, uint8_t ep, uint16_t ct) {
if (ct > 0xFEFF) { ct = 0xFEFF; }
SBuffer s(4);
s.add16(ct);
s.add16(0x000A); // transition time = 1s
- ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s);
- Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
+ ZigbeeSendHue(shortaddr, ep, 0x0300, 0x0A, s);
+ Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep);
light.setColorMode(2); // "ct"
light.setCT(ct);
}
// XY
-void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) {
+void ZigbeeHueXY(uint16_t shortaddr, uint8_t ep, uint16_t x, uint16_t y) {
if (x > 0xFEFF) { x = 0xFEFF; }
if (y > 0xFEFF) { y = 0xFEFF; }
SBuffer s(8);
s.add16(x);
s.add16(y);
s.add16(0x000A); // transition time = 1s
- ZigbeeSendHue(shortaddr, 0x0300, 0x07, s);
- Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
+ ZigbeeSendHue(shortaddr, ep, 0x0300, 0x07, s);
+ Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep);
light.setColorMode(1); // "xy"
light.setX(x);
light.setY(y);
}
// HueSat
-void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) {
+void ZigbeeHueHS(uint16_t shortaddr, uint8_t ep, uint16_t hue, uint8_t sat) {
uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254);
if (sat > 0xFE) { sat = 0xFE; }
SBuffer s(4);
s.add8(hue8);
s.add8(sat);
s.add16(0);
- ZigbeeSendHue(shortaddr, 0x0300, 0x06, s);
- Z_Data_Light & light = zigbee_devices.getLight(shortaddr);
+ ZigbeeSendHue(shortaddr, ep, 0x0300, 0x06, s);
+ Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep);
light.setColorMode(0); // "hs"
light.setSat(sat);
light.setHue(hue);
}
-int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
+int32_t ZigbeeHandleHue(uint16_t shortaddr, uint8_t ep, uint32_t device_id, uint8_t endpoint, String &response) {
uint8_t bri, sat;
uint16_t ct, hue;
- int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
+ int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr, ep);
if (bulbtype < 0) { // respond only if eligible
response = F("{}");
return 200;
@@ -259,9 +286,9 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response
device_id, on ? PSTR("true") : PSTR("false"));
if (on) {
- ZigbeeHuePower(shortaddr, 0x01);
+ ZigbeeHuePower(shortaddr, ep, 0x01);
} else {
- ZigbeeHuePower(shortaddr, 0x00);
+ ZigbeeHuePower(shortaddr, ep, 0x00);
}
response += buf;
resp = true;
@@ -280,7 +307,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response
if (LST_SINGLE <= bulbtype) {
// extend bri value if set to max
if (254 <= bri) { bri = 255; }
- ZigbeeHueDimmer(shortaddr, bri);
+ ZigbeeHueDimmer(shortaddr, ep, bri);
}
resp = true;
}
@@ -304,7 +331,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response
resp = true;
uint16_t xi = x * 65536.0f;
uint16_t yi = y * 65536.0f;
- ZigbeeHueXY(shortaddr, xi, yi);
+ ZigbeeHueXY(shortaddr, ep, xi, yi);
}
bool huesat_changed = false;
@@ -342,7 +369,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response
huesat_changed = true;
}
if (huesat_changed) {
- ZigbeeHueHS(shortaddr, hue, sat);
+ ZigbeeHueHS(shortaddr, ep, hue, sat);
}
resp = true;
}
@@ -358,7 +385,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response
device_id, PSTR("ct"), ct);
response += buf;
if ((LST_COLDWARM == bulbtype) || (LST_RGBW <= bulbtype)) {
- ZigbeeHueCT(shortaddr, ct);
+ ZigbeeHueCT(shortaddr, ep, ct);
}
resp = true;
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
index 13f301b29..fd1aa30f6 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
@@ -315,16 +315,23 @@ ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_SUCCESS) // 65050
// Change #14819 - we now allow some EP to be alreaady declared
ZBM(ZBR_ZDO_ACTIVEEPRSP_SUCESS, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_SUCCESS) // 45050000xxxx - 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
+ 0x00, 0x00 /* nwkaddr */, 0x03 /* activeepcount */, 0xF2, 0x0B, 0x01 /* the actual endpoints */) // 4585000000000003F20B01
// 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 */)
+ 0x00 /* AppNumInClusters */, 0x00 /* AppNumOutClusters */)
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_SUCCESS) // 640000
+ZBM(ZBR_AF_REGISTER_NOERROR, Z_SRSP | Z_AF, AF_REGISTER) // 6400xx -- don't abort if an error occurs
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 */)
+ 0x00 /* AppNumInClusters */, 0x00 /* AppNumOutClusters */)
+// Green Power endpoint 242 0xF2
+ZBM(ZBS_AF_REGISTERF2, Z_SREQ | Z_AF, AF_REGISTER, 0xF2 /* endpoint */, Z_B0(Z_PROF_GP), Z_B1(Z_PROF_GP), //
+ 0x05, 0x61 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
+ 0x00 /* AppNumInClusters */,
+ 0x01 /* AppNumOutClusters */,
+ 0x21,0x00) // 0x0021
// Z_AF:register profile:104, ep:01 - main clusters for router or device
ZBM(ZBS_AF_REGISTER_ALL, 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 */,
@@ -333,7 +340,7 @@ ZBM(ZBS_AF_REGISTER_ALL, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z
0x07,0x00, 0x08,0x00, 0x0A,0x00, 0x02,0x01, // 0x0007, 0x0008, 0x000A, 0X0102
0x00,0x03, 0x00,0x04, 0x02,0x04, 0x03,0x04, // 0x0300, 0x0400, 0x0402, 0x0403
0x05,0x04, 0x06,0x04, // 0x0405, 0x0406
- 0x00 /* AppNumInClusters */)
+ 0x00 /* AppNumOutClusters */)
// Z_ZDO:mgmtPermitJoinReq
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000
@@ -467,7 +474,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredCoord)
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) // set any failure to ABORT
ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator
- ZI_WAIT_RECV(5000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
+ ZI_WAIT_RECV(10000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
ZI_WAIT_UNTIL_FUNC(20000, AREQ_STARTUPFROMAPP, &ZNP_ReceiveStateChange) // wait for async message that coordinator started, max 20s
ZI_GOTO(ZIGBEE_LABEL_COORD_STARTED)
@@ -487,7 +494,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_WAIT_RECV(2000, ZBR_W_BDB_CHANN_OK)
// all is good, we can start
ZI_SEND(ZBS_BDB_START_COMMIS) // start coordinator
- ZI_WAIT_RECV(5000, ZBR_BDB_START_COMMIS) // wait for sync ack of command
+ ZI_WAIT_RECV(10000, ZBR_BDB_START_COMMIS) // wait for sync ack of command
ZI_WAIT_UNTIL_FUNC(20000, AREQ_STARTUPFROMAPP, &ZNP_ReceiveStateChange) // wait for async message that coordinator started, max 20s
// ======================================================================
@@ -506,14 +513,17 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
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)
+ ZI_SEND(ZBS_AF_REGISTERF2) // Z_AF register for endpoint F2, profile 0xa1e0 Green Power
+ ZI_WAIT_RECV(1000, ZBR_AF_REGISTER_NOERROR) // don't abort if endpoint F2 was not accepted
// Write again channels, see https://github.com/Koenkk/zigbee-herdsman/blob/37bea20ba04ee5d4938abc21a7569b43f831de32/src/adapter/z-stack/adapter/startZnp.ts#L244-L245
ZI_SEND(ZBS_W_CHANN) // write CHANNEL
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
// 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)
+ // This is not really necessary and will fail if F2 was not allowed
+ // 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)
@@ -529,6 +539,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_CALL(&Z_Load_Devices, 0)
ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0)
+ ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
@@ -966,6 +977,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_CALL(&Z_Load_Devices, 0)
ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0)
+ ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino
new file mode 100644
index 000000000..4a1ef2b59
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino
@@ -0,0 +1,479 @@
+/*
+ xdrv_23_zigbee.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+const char Z_MUL[] PROGMEM = "mul:";
+const char Z_DIV[] PROGMEM = "div:";
+const char Z_MANUF[] PROGMEM = "manuf:";
+const char Z_ADD[] PROGMEM = "add:";
+
+char * Z_subtoken(char * token, const char * prefix) {
+ size_t prefix_len = strlen_P(prefix);
+ if (!strncmp_P(token, prefix, prefix_len)) {
+ return token + prefix_len;
+ }
+ return nullptr;
+}
+
+// global singleton
+Z_plugin_templates g_plugin_templates;
+
+class Z_attribute_synonym Z_plugin_matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ const Z_attribute_synonym * attr_syn;
+ attr_syn = g_plugin_templates.matchAttributeSynonym(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ cluster, attribute);
+
+ Z_attribute_synonym syn;
+ if (attr_syn != nullptr) {
+ syn = *attr_syn;
+ }
+ return syn;
+}
+
+Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ const Z_plugin_attribute * attr_tmpl;
+ attr_tmpl = g_plugin_templates.matchAttributeById(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ cluster, attribute);
+
+ Z_attribute_match attr;
+ if (attr_tmpl != nullptr) {
+ attr.cluster = attr_tmpl->cluster;
+ attr.attribute = attr_tmpl->attribute;
+ attr.name = attr_tmpl->name;
+ attr.zigbee_type = attr_tmpl->type;
+ attr.multiplier = attr_tmpl->multiplier;
+ attr.divider = attr_tmpl->divider;
+ attr.base = attr_tmpl->base;
+ attr.manuf = attr_tmpl->manuf;
+ }
+ return attr;
+}
+
+Z_attribute_match Z_plugin_matchAttributeByName(const char *model, const char *manufacturer, const char * name) {
+ const Z_plugin_attribute * attr_tmpl;
+ Z_attribute_match attr;
+
+ if (name != nullptr) {
+ attr_tmpl = g_plugin_templates.matchAttributeByName(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ name);
+
+ if (attr_tmpl != nullptr) {
+ attr.cluster = attr_tmpl->cluster;
+ attr.attribute = attr_tmpl->attribute;
+ attr.name = attr_tmpl->name;
+ attr.zigbee_type = attr_tmpl->type;
+ attr.multiplier = attr_tmpl->multiplier;
+ attr.divider = attr_tmpl->divider;
+ attr.base = attr_tmpl->base;
+ attr.manuf = attr_tmpl->manuf;
+ }
+ }
+ return attr;
+}
+
+bool Zb_readline(class File *f, char* buf, size_t size) {
+ bool eof = 0;
+ while (1) {
+ // read line
+ bool comment = false; // did we encounter '#', if so ignore anything until '\n'
+ char * p = buf;
+ while (1) {
+ int c = f->read();
+ if (c == -1) { eof = true; break; } // EOF reached
+ if (c == '#') { comment = true; } // rest of line is ignored
+ if (c == '\n') { break; } // end of line
+ if (!comment) {
+ if (p < buf + size - 1) {
+ *p++ = c; // append character
+ } else {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad line exceeds 96 bytes, aborting");
+ return false;
+ }
+ }
+ }
+ int32_t ret = p - buf; // len in bytes
+ if (eof && ret == 0) { return false; } // nothing more to read
+ // found something, don't add the \n since we don't need it
+ buf[ret] = 0; // add string terminator
+ RemoveSpace(buf); // remove anything that looks like a space, tab, crlf...
+ // AddLog(LOG_LEVEL_INFO, "ZIG: ZbRead>'%s'", buf);
+ return true;
+ }
+}
+
+#ifdef USE_UFILESYS
+extern FS *ffsp;
+#endif
+
+// load a file from filesystem
+// returns `true` if success
+bool ZbLoad(const char *filename_raw) {
+
+#ifdef USE_UFILESYS
+ if (ffsp) {
+ // first unload previsou definitions
+ ZbUnload(filename_raw);
+
+ String filename = filename_raw;
+ if (filename_raw[0] != '/') {
+ filename = "/";
+ filename += filename_raw;
+ }
+ File fp;
+ fp = ffsp->open(filename.c_str(), "r");
+
+ if (fp <= 0) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: unable to load file '%s'", filename.c_str());
+ return false;
+ }
+
+ char * filename_imported = nullptr;
+ Z_plugin_template * tmpl = nullptr; // current template with matchers and attributes
+ bool new_matchers = false; // indicates that we have finished the current matchers
+ char buf_line[96]; // max line is 96 bytes (comments don't count)
+
+ // read the first 6 chars
+ bool invalid_header = false;
+ static const char Z2T_HEADER_V1[] PROGMEM = "#Z2Tv1";
+ for (uint32_t i = 0; i < 6; i++) {
+ int c = fp.read();
+ if (c < 0) {
+ invalid_header = true;
+ break;
+ }
+ buf_line[i] = c;
+ buf_line[i+1] = 0;
+ }
+ if (!invalid_header) {
+ if (strcmp_P(buf_line, Z2T_HEADER_V1) != 0) {
+ invalid_header = true;
+ }
+ }
+
+ if (invalid_header) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' invalid header", filename_raw);
+ return false;
+ }
+
+ // parse line by line
+ while (1) {
+ if (!Zb_readline(&fp, buf_line, sizeof(buf_line))) {
+ // EOF
+ break;
+ }
+
+ // at first valid line, we instanciate a new plug-in instance and assign a filemane
+ if (filename_imported == nullptr) {
+ // allocate only once the filename for multiple entries
+ // freed only by `ZbUnload`
+ filename_imported = (char*) malloc(strlen(filename.c_str())+1);
+ strcpy(filename_imported, filename.c_str());
+ }
+
+ // there is a non-empty line, containing no space/tab/crlf
+ // depending on the first char, parse either device name or cluster/attribute+name
+ if (buf_line[0] == ':') {
+ if (tmpl == nullptr || new_matchers) {
+ tmpl = &g_plugin_templates.addToLast();
+ tmpl->filename = filename_imported;
+ new_matchers = false;
+ }
+ // parse device name
+ char *rest = buf_line + 1; // skip first char ':'
+ char *token = strtok_r(rest, ",", &rest);
+ Z_plugin_matcher & matcher = tmpl->matchers.addToLast();
+ if (token != nullptr) {
+ matcher.setModel(token);
+ }
+ token = strtok_r(rest, ",", &rest);
+ if (token != nullptr) {
+ matcher.setManuf(token);
+ }
+ } else {
+ if (tmpl == nullptr) {
+ continue;
+ }
+ new_matchers = true;
+ char *rest = buf_line;
+ char *token = strtok_r(rest, ",", &rest);
+ if (token == nullptr) {
+ continue;
+ }
+
+ // detect if token contains '=', if yes it is a synonym
+ char * delimiter_equal = strchr(token, '=');
+
+ if (delimiter_equal == nullptr) {
+ // NORMAL ATTRIBUTE
+ // token is of from '0000/0000' or '0000/0000%00'
+ char * delimiter_slash = strchr(token, '/');
+ char * delimiter_percent = strchr(token, '%');
+ if (delimiter_slash == nullptr || (delimiter_percent != nullptr && delimiter_slash > delimiter_percent)) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' wrong delimiter '%s'", filename_raw, token);
+ }
+
+ uint16_t attr_id = 0xFFFF;
+ uint16_t cluster_id = 0xFFFF;
+ uint8_t type_id = Zunk;
+ int8_t multiplier = 1;
+ int8_t divider = 1;
+ int16_t base = 0;
+ char * name = nullptr;
+ uint16_t manuf = 0;
+
+ cluster_id = strtoul(token, &delimiter_slash, 16);
+ if (!delimiter_percent) {
+ attr_id = strtoul(delimiter_slash+1, nullptr, 16);
+ } else {
+ attr_id = strtoul(delimiter_slash+1, &delimiter_percent, 16);
+ type_id = Z_getTypeByName(delimiter_percent+1);
+ }
+ // NAME of the attribute
+ token = strtok_r(rest, ",", &rest);
+ if (token == nullptr) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' ignore missing name '%s'", filename_raw, buf_line);
+ continue;
+ }
+ name = token;
+ // ADDITIONAL ELEMENTS?
+ // Ex: `manuf:1037`
+ while (token = strtok_r(rest, ",", &rest)) {
+ char * sub_token;
+ // look for multiplier
+ if (sub_token = Z_subtoken(token, Z_MUL)) {
+ multiplier = strtol(sub_token, nullptr, 10);
+ }
+ // look for divider
+ else if (sub_token = Z_subtoken(token, Z_DIV)) {
+ divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
+ }
+ // look for offset (base)
+ else if (sub_token = Z_subtoken(token, Z_ADD)) {
+ base = strtol(sub_token, nullptr, 10); // negative to indicate divider
+ }
+ // look for `manuf:HHHH`
+ else if (sub_token = Z_subtoken(token, Z_MANUF)) {
+ manuf = strtoul(sub_token, nullptr, 16);
+ }
+ else {
+ AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
+ }
+ }
+
+ // token contains the name of the attribute
+ Z_plugin_attribute & plugin_attr = tmpl->attributes.addToLast();
+ plugin_attr.cluster = cluster_id;
+ plugin_attr.attribute = attr_id;
+ plugin_attr.type = type_id;
+ plugin_attr.setName(name);
+ plugin_attr.multiplier = multiplier;
+ plugin_attr.divider = divider;
+ plugin_attr.base = base;
+ plugin_attr.manuf = manuf;
+ } else {
+ // ATTRIBUTE SYNONYM
+ // token is of from '0000/0000=0000/0000,1'
+ char * rest2 = token;
+ char * tok2 = strtok_r(rest2, "=", &rest2);
+ char * delimiter_slash = strchr(tok2, '/');
+ uint16_t cluster_id = strtoul(tok2, &delimiter_slash, 16);
+ uint16_t attr_id = strtoul(delimiter_slash+1, nullptr, 16);
+ tok2 = strtok_r(rest2, "=", &rest2);
+ char * delimiter_slash2 = strchr(tok2, '/');
+ uint16_t new_cluster_id = strtoul(tok2, &delimiter_slash2, 16);
+ uint16_t new_attr_id = strtoul(delimiter_slash2+1, nullptr, 16);
+ int8_t multiplier = 1;
+ int8_t divider = 1;
+ int16_t base = 0;
+
+ // ADDITIONAL ELEMENTS?
+ while (token = strtok_r(rest, ",", &rest)) {
+ char * sub_token;
+ // look for multiplier
+ if (sub_token = Z_subtoken(token, Z_MUL)) {
+ multiplier = strtol(sub_token, nullptr, 10);
+ }
+ // look for divider
+ else if (sub_token = Z_subtoken(token, Z_DIV)) {
+ divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
+ }
+ // look for offset (base)
+ else if (sub_token = Z_subtoken(token, Z_ADD)) {
+ base = strtol(sub_token, nullptr, 10); // negative to indicate divider
+ }
+ else {
+ AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
+ }
+ }
+ // create the synonym
+ Z_attribute_synonym & syn = tmpl->synonyms.addToLast();
+ syn.cluster = cluster_id;
+ syn.attribute = attr_id;
+ syn.new_cluster = new_cluster_id;
+ syn.new_attribute = new_attr_id;
+ syn.multiplier = multiplier;
+ syn.divider = divider;
+ syn.base = base;
+ }
+ }
+ }
+ } else {
+ AddLog(LOG_LEVEL_ERROR, "ZIG: filesystem not enabled");
+ }
+#else
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad requires file system");
+#endif
+ return true;
+}
+
+// Unlaod previously loaded definitions
+bool ZbUnload(const char *filename_raw) {
+ String filename = filename_raw;
+ if (filename_raw[0] != '/') {
+ filename = "/";
+ filename += filename_raw;
+ }
+
+ char * filename_registered = nullptr; // internal allocation for filename
+ for (const Z_plugin_template & tmpl : g_plugin_templates) {
+ bool to_be_freed = false;
+ if (filename_registered) {
+ // if filename_registered is not NULL, compare pointers
+ if (tmpl.filename == filename_registered) { to_be_freed = true; }
+ } else {
+ if (strcmp(tmpl.filename, filename.c_str()) == 0) {
+ filename_registered = tmpl.filename;
+ to_be_freed = true;
+ }
+ }
+ // check if we remove this node
+ if (to_be_freed) {
+ g_plugin_templates.remove(&tmpl);
+ }
+ }
+ // free memory for filename
+ if (filename_registered) {
+ free(filename_registered);
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbUnload '%s' sucessful", filename_raw);
+ return true;
+ }
+ return false;
+}
+
+// append modifiers like mul/div/manuf
+void Z_AppendModifiers(char * buf, size_t buf_len, int8_t multiplier, int8_t divider, int16_t base, uint16_t manuf) {
+ if (multiplier != 0 && multiplier != 1) {
+ ext_snprintf_P(buf, buf_len, "%s,%s%i", buf, Z_MUL, multiplier);
+ }
+ if (divider != 0 && divider != 1) {
+ ext_snprintf_P(buf, buf_len, "%s,%s%i", buf, Z_DIV, divider);
+ }
+ if (base != 0) {
+ ext_snprintf_P(buf, buf_len, "%s,%s%i", buf, Z_ADD, base);
+ }
+ if (manuf) {
+ ext_snprintf_P(buf, buf_len, "%s,%s%04X", buf, Z_MANUF, manuf);
+ }
+}
+
+// Dump the ZbLoad structure in a format compatible with ZbLoad
+void ZbLoadDump(void) {
+ char buf[96];
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad dump all current information");
+ AddLog(LOG_LEVEL_INFO, "====> START");
+
+ for (const Z_plugin_template & tmpl : g_plugin_templates) {
+ if (tmpl.filename != nullptr) {
+ ext_snprintf_P(buf, sizeof(buf), "# imported from '%s'", tmpl.filename);
+ AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf);
+ }
+ // marchers
+ if (tmpl.matchers.length() == 0) {
+ ext_snprintf_P(buf, sizeof(buf), ": # no matcher");
+ AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf);
+ } else {
+ for (const Z_plugin_matcher & matcher : tmpl.matchers) {
+ ext_snprintf_P(buf, sizeof(buf), ":%s,%s", matcher.model ? matcher.model : "", matcher.manufacturer ? matcher.manufacturer : "");
+ AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf);
+ }
+ }
+ // attributes
+ if (tmpl.attributes.length() == 0 && tmpl.synonyms.length() == 0) {
+ // no content, output an empty line
+ AddLog(LOG_LEVEL_INFO, "");
+ } else {
+ for (const Z_plugin_attribute & attr : tmpl.attributes) {
+ ext_snprintf_P(buf, sizeof(buf), "%04X/%04X", attr.cluster, attr.attribute);
+ // add type if known
+ if (attr.type != Zunk) {
+ char type_str[16];
+ Z_getTypeByNumber(type_str, sizeof(type_str), attr.type);
+ ext_snprintf_P(buf, sizeof(buf), "%s%%%s", buf, type_str);
+ }
+ ext_snprintf_P(buf, sizeof(buf), "%s,%s", buf, attr.name);
+ Z_AppendModifiers(buf, sizeof(buf), attr.multiplier, attr.divider, attr.base, attr.manuf);
+ AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf);
+ }
+ for (const Z_attribute_synonym & syn : tmpl.synonyms) {
+ ext_snprintf_P(buf, sizeof(buf), "%04X/%04X=%04X/%04X", syn.cluster, syn.attribute, syn.new_cluster, syn.new_attribute);
+ Z_AppendModifiers(buf, sizeof(buf), syn.multiplier, syn.divider, syn.base, 0);
+ AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf);
+ }
+ }
+ }
+
+ AddLog(LOG_LEVEL_INFO, "<==== END");
+}
+
+// Auto-load all files ending with '.zb'
+void ZbAutoload(void) {
+#ifdef USE_UFILESYS
+ if (ffsp) {
+ File dir = ffsp->open("/", "r");
+ if (dir) {
+ dir.rewindDirectory();
+ while (1) {
+ File entry = dir.openNextFile();
+ if (!entry) { break; }
+ const char * fn = entry.name();
+ if (strcmp(fn, ".") && strcmp(fn, "..")) {
+ // check suffix
+ size_t l = strlen(fn);
+ if (l > 3) {
+ if (fn[l-3] == '.' && fn[l-2] == 'z' && fn[l-1] == 'b') {
+ bool ret = ZbLoad(fn);
+ if (ret) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' loaded successfully", fn);
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+ }
+#endif // USE_UFILESYS
+}
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
index 14b64109a..db21e801f 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
@@ -666,7 +666,7 @@ int32_t Z_ReceiveActiveEp(int32_t res, const SBuffer &buf) {
const uint8_t Z_bindings[] PROGMEM = {
Cx0001, Cx0006, Cx0008, Cx0102, Cx0201, Cx0300,
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
- Cx0500,
+ Cx0500, Cx0B04,
};
int32_t Z_ClusterToCxBinding(uint16_t cluster) {
@@ -714,6 +714,11 @@ void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf,
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0500, endpoint, Z_CAT_CIE_ENROLL, 1 /* zone */, &Z_SendCIEZoneEnrollResponse);
}
+ // if Plug, request the multipliers and divisors for Voltage, Current and Power
+ if (bitRead(cluster_in_map, Z_ClusterToCxBinding(0x0B04))) {
+ zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0B04, endpoint, Z_CAT_READ_ATTRIBUTE, 0 /* ignore */, &Z_SendSinglePlugMulDivAttributesRead);
+ }
+
// enqueue bind requests
for (uint32_t i=0; i 0: divide by the multiplier
-// multiplier < 0: multiply by the -multiplier (positive)
-void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
+void ZbApplyMultiplierForWrites(double &val_d, int8_t multiplier, int8_t divider, int8_t base) {
+ if (0 != base) {
+ val_d = val_d - base;
+ }
if ((0 != multiplier) && (1 != multiplier)) {
- if (multiplier > 0) { // inverse of decoding
- val_d = val_d / multiplier;
- } else {
- val_d = val_d * (-multiplier);
- }
+ val_d = val_d / multiplier;
+ }
+ if ((0 != divider) && (1 != divider)) {
+ val_d = val_d * divider;
}
}
@@ -239,15 +243,15 @@ bool ZbTuyaWrite(SBuffer & buf, const Z_attribute & attr) {
double val_d = attr.getOptimisticDouble();
const char * val_str = attr.getStr();
- if (attr.key_is_str) { return false; } // couldn't find attr if so skip
- if (attr.isNum() && (1 != attr.attr_multiplier)) {
- ZbApplyMultiplier(val_d, attr.attr_multiplier);
+ if (attr.key_is_str || attr.key_is_cmd) { return false; } // couldn't find attr if so skip
+ if (attr.isNum()) {
+ ZbApplyMultiplierForWrites(val_d, attr.attr_multiplier, attr.attr_divider, 0);
}
uint32_t u32 = val_d;
int32_t i32 = val_d;
- uint8_t tuyatype = (attr.key.id.attr_id >> 8);
- uint8_t dpid = (attr.key.id.attr_id & 0xFF);
+ uint8_t tuyatype = (attr.attr_id >> 8);
+ uint8_t dpid = (attr.attr_id & 0xFF);
buf.add8(dpid);
buf.add8(tuyatype);
@@ -298,13 +302,13 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
double val_d = attr.getOptimisticDouble();
const char * val_str = attr.getStr();
- if (attr.key_is_str) { return false; } // couldn't find attr if so skip
- if (attr.isNum() && (1 != attr.attr_multiplier)) {
- ZbApplyMultiplier(val_d, attr.attr_multiplier);
+ if (attr.key_is_str && attr.key_is_cmd) { return false; } // couldn't find attr if so skip
+ if (attr.isNum()) {
+ ZbApplyMultiplierForWrites(val_d, attr.attr_multiplier, attr.attr_divider, 0);
}
// push the value in the buffer
- buf.add16(attr.key.id.attr_id); // prepend with attribute identifier
+ buf.add16(attr.attr_id); // prepend with attribute identifier
if (prepend_status_ok) {
buf.add8(Z_SUCCESS); // status OK = 0x00
}
@@ -313,7 +317,7 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
if (res < 0) {
// remove the attribute type we just added
// buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3));
- AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " %04X/%04X '0x%02X'"), attr.key.id.cluster, attr.key.id.attr_id, attr.attr_type);
+ AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " %04X/%04X '0x%02X'"), attr.cluster, attr.attr_id, attr.attr_type);
return false;
}
return true;
@@ -339,24 +343,33 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
Z_attribute attr;
attr.setKeyName(key.getStr());
- if (Z_parseAttributeKey(attr, tuya_protocol ? 0xEF00 : 0xFFFF)) { // favor tuya protocol if needed
+ if (Z_parseAttributeKey(zcl.shortaddr, attr, tuya_protocol ? 0xEF00 : 0xFFFF)) { // favor tuya protocol if needed
// Buffer ready, do some sanity checks
// all attributes must use the same cluster
if (0xFFFF == zcl.cluster) {
- zcl.cluster = attr.key.id.cluster; // set the cluster for this packet
- } else if (zcl.cluster != attr.key.id.cluster) {
+ zcl.cluster = attr.cluster; // set the cluster for this packet
+ } else if (zcl.cluster != attr.cluster) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
return;
}
+ // check for manuf code
+ if (attr.manuf) {
+ if (zcl.manuf != 0 && zcl.manuf != attr.manuf) {
+ AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "conflicting manuf code 0x%04X (was 0x%04X)"), attr.manuf, zcl.manuf);
+ } else {
+ zcl.manuf = attr.manuf;
+ }
+ }
+
} else {
if (attr.key_is_str) {
- Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNKNOWN_ATTRIBUTE " "), key);
+ Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNKNOWN_ATTRIBUTE " "), key.getStr());
return;
}
if (Zunk == attr.attr_type) {
- Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " "), key);
+ Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " "), key.getStr());
return;
}
}
@@ -408,9 +421,14 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
// read ReportableChange
JsonParserToken val_attr_rc = attr_config[PSTR("ReportableChange")];
if (val_attr_rc) {
- val_d = val_attr_rc.getFloat();
+ // If value is `null` then we send 0xFFFF for invalid value
val_str = val_attr_rc.getStr();
- ZbApplyMultiplier(val_d, attr.attr_multiplier);
+ if (!val_attr_rc.isNull()) {
+ val_d = val_attr_rc.getFloat();
+ ZbApplyMultiplierForWrites(val_d, attr.attr_multiplier, attr.attr_divider, 0);
+ } else {
+ val_d = NAN;
+ }
}
// read TimeoutPeriod
@@ -421,7 +439,7 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
// all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1
// common bytes
buf.add8(attr_direction ? 0x01 : 0x00);
- buf.add16(attr.key.id.attr_id);
+ buf.add16(attr.attr_id);
if (attr_direction) {
buf.add16(attr_timeout);
} else {
@@ -538,6 +556,11 @@ void ZbSendSend(class JsonParserToken val_cmd, ZCLFrame & zcl) {
// Now parse the string to extract cluster, command, and payload
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
// where AAAA is the cluster number, BB the command number, CCCC... the payload
+ // Possible delimiters:
+ // "AAAA_BB/...": general cluster, sent to device (direction == 0)
+ // "AAAA^BB/...": general cluster, recevied from device (direction == 1)
+ // "AAAA!BB/...": cluster specific, sent to device (direction == 0)
+ // "AAAA?BB/...": cluster specific, recevied from device (direction == 1)
// First delimiter is '_' for a global command, or '!' for a cluster specific command
const char * data = val_cmd.getStr();
uint16_t local_cluster_id = parseHex(&data, 4);
@@ -551,8 +574,9 @@ void ZbSendSend(class JsonParserToken val_cmd, ZCLFrame & zcl) {
}
// delimiter
- if (('_' == *data) || ('!' == *data)) {
- if ('_' == *data) { zcl.clusterSpecific = false; }
+ if (('_' == *data) || ('^' == *data) || ('!' == *data) || ('?' == *data)) {
+ if ('_' == *data || '^' == *data) { zcl.clusterSpecific = false; }
+ if ('^' == *data || '?' == *data) { zcl.direction = true; }
data++;
} else {
ResponseCmndChar_P(PSTR(D_ZIGBEE_WRONG_DELIMITER));
@@ -623,34 +647,34 @@ void ZbSendRead(JsonParserToken val_attr, ZCLFrame & zcl) {
bool found = false;
// scan attributes to find by name, and retrieve type
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t local_attr_id = pgm_read_word(&converter->attribute);
- uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
- // uint8_t local_type_id = pgm_read_byte(&converter->type);
-
- if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key.getStr(), Z_strings + pgm_read_word(&converter->name_offset)))) {
- // match name
- // check if there is a conflict with cluster
- // TODO
- if (!(value.getBool()) && attr_item_offset) {
- // If value is false (non-default) then set direction to 1 (for ReadConfig)
- attrs[actual_attr_len] = 0x01;
+ Z_attribute_match matched_attr = Z_findAttributeMatcherByName(zcl.shortaddr, key.getStr());
+ if (matched_attr.found()) {
+ // match name
+ // check if there is a conflict with cluster
+ if (!(value.getBool()) && attr_item_offset) {
+ // If value is false (non-default) then set direction to 1 (for ReadConfig)
+ attrs[actual_attr_len] = 0x01;
+ }
+ actual_attr_len += attr_item_offset;
+ attrs[actual_attr_len++] = matched_attr.attribute & 0xFF;
+ attrs[actual_attr_len++] = matched_attr.attribute >> 8;
+ actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
+ found = true;
+ // check cluster
+ if (!zcl.validCluster()) {
+ zcl.cluster = matched_attr.cluster;
+ } else if (zcl.cluster != matched_attr.cluster) {
+ ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
+ if (attrs) { free(attrs); }
+ return;
+ }
+ // check for manuf code
+ if (matched_attr.manuf) {
+ if (zcl.manuf != 0 && zcl.manuf != matched_attr.manuf) {
+ AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "conflicting manuf code 0x%04X (was 0x%04X)"), matched_attr.manuf, zcl.manuf);
+ } else {
+ zcl.manuf = matched_attr.manuf;
}
- actual_attr_len += attr_item_offset;
- attrs[actual_attr_len++] = local_attr_id & 0xFF;
- attrs[actual_attr_len++] = local_attr_id >> 8;
- actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
- found = true;
- // check cluster
- if (!zcl.validCluster()) {
- zcl.cluster = local_cluster_id;
- } else if (zcl.cluster != local_cluster_id) {
- ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
- if (attrs) { free(attrs); }
- return;
- }
- break; // found, exit loop
}
}
if (!found) {
@@ -719,8 +743,17 @@ void CmndZbSend(void) {
// parse "Device" and "Group"
JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)];
if (val_device) {
- zcl.shortaddr = zigbee_devices.parseDeviceFromName(val_device.getStr()).shortaddr;
- if (!zcl.validShortaddr()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_INVALID_PARAM)); return; }
+ uint16_t parsed_shortaddr = BAD_SHORTADDR;
+ zcl.shortaddr = zigbee_devices.parseDeviceFromName(val_device.getStr(), &parsed_shortaddr, &zcl.dstendpoint).shortaddr;
+ if (!zcl.validShortaddr()) {
+ if (parsed_shortaddr != BAD_SHORTADDR) {
+ // we still got a short address
+ zcl.shortaddr = parsed_shortaddr;
+ } else {
+ ResponseCmndChar_P(PSTR(D_ZIGBEE_INVALID_PARAM));
+ return;
+ }
+ }
}
if (!zcl.validShortaddr()) { // if not found, check if we have a group
JsonParserToken val_group = root[PSTR(D_CMND_ZIGBEE_GROUP)];
@@ -735,9 +768,17 @@ void CmndZbSend(void) {
// read other parameters
zcl.cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), zcl.cluster);
- zcl.dstendpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), zcl.dstendpoint);
zcl.manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), zcl.manuf);
+ // read dest endpoint and check if it's not in conflict with ep name
+ uint8_t json_endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), 0);
+ if (zcl.dstendpoint && json_endpoint && zcl.dstendpoint != json_endpoint) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("ZIG: conflicting endpoints, from name:%i from json:%i"), zcl.dstendpoint, json_endpoint);
+ ResponseCmndChar_P(PSTR(D_ZIGBEE_CONFLICTING_ENDPOINTS));
+ return;
+ }
+ zcl.dstendpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), zcl.dstendpoint);
+
// infer endpoint
if (!zcl.validShortaddr()) {
zcl.dstendpoint = 0xFF; // endpoint not used for group addresses, so use a dummy broadcast endpoint
@@ -749,6 +790,10 @@ void CmndZbSend(void) {
ResponseCmndChar_P(PSTR("Missing endpoint"));
return;
}
+ // Special case for Green Power, if dstendpoint is 0xF2, then source endpoint should also be 0xF2
+ if (zcl.dstendpoint == 0xF2) {
+ zcl.srcendpoint = 0xF2;
+ }
// from here endpoint is valid and non-zero
// cluster may be already specified or 0xFFFF
@@ -863,7 +908,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (val_cluster) {
cluster = val_cluster.getUInt(cluster); // first convert as number
if (0 == cluster) {
- zigbeeFindAttributeByName(val_cluster.getStr(), &cluster, nullptr, nullptr);
+ Z_attribute_match attr_matched = Z_findAttributeMatcherByName(BAD_SHORTADDR, val_cluster.getStr());
+ if (attr_matched.found()) {
+ cluster = attr_matched.cluster;
+ }
}
}
@@ -963,7 +1011,7 @@ void CmndZbUnbind(void) {
//
void CmndZbLeave(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload).shortaddr;
+ uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload).shortaddr;
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
#ifdef USE_ZIGBEE_ZNP
@@ -995,7 +1043,7 @@ void CmndZbLeave(void) {
void CmndZbBindState_or_Map(bool map) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t parsed_shortaddr;;
- uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, &parsed_shortaddr, XdrvMailbox.payload).shortaddr;
+ uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, &parsed_shortaddr, nullptr, XdrvMailbox.payload).shortaddr;
if (BAD_SHORTADDR == shortaddr) {
if ((map) && (parsed_shortaddr != shortaddr)) {
shortaddr = parsed_shortaddr; // allow a non-existent address when ZbMap
@@ -1051,13 +1099,13 @@ void ZigbeeMapAllDevices(void) {
//
void CmndZbMap(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- RemoveSpace(XdrvMailbox.data);
+ TrimSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) == 0) {
ZigbeeMapAllDevices();
ResponseCmndDone();
} else {
- CmndZbBindState_or_Map(true);
+ CmndZbBindState_or_Map(true);
}
}
@@ -1071,7 +1119,7 @@ void CmndZbProbe(void) {
//
void CmndZbProbeOrPing(boolean probe) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload).shortaddr;
+ uint16_t shortaddr = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload).shortaddr;
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
// set a timer for Reachable - 2s default value
@@ -1100,26 +1148,40 @@ void CmndZbName(void) {
// ZbName - display the current friendly name
// ZbName , - remove friendly name
//
+ // New:
+ // ZbName ,, - assign a friendly name to a endpoint
+ // ZbName ,, - remove name to endpoint
+ //
// Where can be: short_addr, long_addr, device_index, friendly_name
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
// check if parameters contain a comma ','
- char *p;
- strtok_r(XdrvMailbox.data, ",", &p);
+ char *p = XdrvMailbox.data;
+ char *device_id = strsep(&p, ","); // zigbee identifier
+ bool has_comma = (p != nullptr);
+ char *new_friendlyname = strsep(&p, ","); // friendly name
+ int32_t ep = (p != nullptr) ? strtol(p, nullptr, 10) : 0; // get endpoint number, or `0` if none
// parse first part,
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // it's the only case where we create a new device
+ Z_Device & device = zigbee_devices.parseDeviceFromName(device_id, nullptr, nullptr, XdrvMailbox.payload); // it's the only case where we create a new device
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
- if (p == nullptr) {
+ if (!has_comma) {
const char * friendlyName = device.friendlyName;
- Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), device.shortaddr, friendlyName ? friendlyName : "");
} else {
- if (strlen(p) > 32) { p[32] = 0x00; } // truncate to 32 chars max
- device.setFriendlyName(p);
- Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), device.shortaddr, p);
+ if (new_friendlyname != nullptr && strlen(new_friendlyname) > 32) { new_friendlyname[32] = 0x00; } // truncate to 32 chars max
+ if (ep == 0) {
+ device.setFriendlyName(new_friendlyname);
+ } else {
+ if (!device.setEPName(ep, new_friendlyname)) {
+ ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_ENDPOINT)); return;
+ }
+ }
}
+ Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), device.shortaddr, device.friendlyName ? device.friendlyName : "");
+ device.ep_names.ResponseAppend();
+ ResponseAppend_P(PSTR("}}"));
}
//
@@ -1141,7 +1203,7 @@ void CmndZbModelId(void) {
strtok_r(XdrvMailbox.data, ",", &p);
// parse first part,
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
if (p != nullptr) {
@@ -1156,7 +1218,7 @@ void CmndZbModelId(void) {
// Specify, read or erase a Light type for Hue/Alexa integration
void CmndZbLight(void) {
// Syntax is:
- // ZbLight , - assign a bulb type 0-5
+ // ZbLight , - assign a bulb type 0-5, or -1 to remove
// ZbLight - display the current bulb type and status
//
// Where can be: short_addr, long_addr, device_index, friendly_name
@@ -1164,18 +1226,20 @@ void CmndZbLight(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
// check if parameters contain a comma ','
- char *p;
- strtok_r(XdrvMailbox.data, ", ", &p);
+ char *p = XdrvMailbox.data;
+ char *device_id = strsep(&p, ","); // zigbee identifier
+ char *bulbtype_str = strsep(&p, ","); // friendly name
+ int32_t ep = (p != nullptr) ? strtol(p, nullptr, 10) : 0; // get endpoint number, or `0` if none
// parse first part,
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(device_id, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
- if (p) {
- int8_t bulbtype = strtol(p, nullptr, 10);
+ if (bulbtype_str != nullptr) {
+ int8_t bulbtype = strtol(bulbtype_str, nullptr, 10);
if (bulbtype > 5) { bulbtype = 5; }
if (bulbtype < -1) { bulbtype = -1; }
- device.setLightChannels(bulbtype);
+ device.setLightChannels(bulbtype, ep); // assign by default to first endpoint, or 0 if no endpoint known
}
Z_attribute_list attr_list;
device.jsonLightState(attr_list);
@@ -1209,10 +1273,10 @@ void CmndZbOccupancy(void) {
// check if parameters contain a comma ','
char *p;
- strtok_r(XdrvMailbox.data, ", ", &p);
+ strtok_r(XdrvMailbox.data, ",", &p);
// parse first part,
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
int8_t occupancy_time = -1;
@@ -1239,7 +1303,7 @@ void CmndZbOccupancy(void) {
//
void CmndZbForget(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
// everything is good, we can send the command
@@ -1261,7 +1325,7 @@ void CmndZbInfo_inner(const Z_Device & device) {
}
void CmndZbInfo(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- RemoveSpace(XdrvMailbox.data);
+ TrimSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) == 0) {
// if empty, dump for all values
@@ -1269,7 +1333,7 @@ void CmndZbInfo(void) {
CmndZbInfo_inner(device);
}
} else { // try JSON
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
// everything is good, we can send the command
@@ -1306,6 +1370,53 @@ void CmndZbSave(void) {
ResponseCmndDone();
}
+//
+// Command `ZbLoad`
+// Load a device specific zigbee template
+//
+void CmndZbLoad(void) {
+ // can be called before Zigbee is initialized
+ TrimSpace(XdrvMailbox.data);
+
+ bool ret = true;;
+ if (strcmp(XdrvMailbox.data, "*") == 0) {
+ ZbAutoload();
+ } else {
+ ret = ZbLoad(XdrvMailbox.data);
+ }
+ if (ret) {
+ ResponseCmndDone();
+ } else {
+ ResponseCmndError();
+ }
+}
+
+//
+// Command `ZbUnload`
+// Unload a template previously loaded
+//
+void CmndZbUnload(void) {
+ // can be called before Zigbee is initialized
+ TrimSpace(XdrvMailbox.data);
+
+ bool ret = ZbUnload(XdrvMailbox.data);
+ if (ret) {
+ ResponseCmndDone();
+ } else {
+ ResponseCmndError();
+ }
+}
+
+//
+// Command `ZbLoadDump`
+// Load a device specific zigbee template
+//
+void CmndZbLoadDump(void) {
+ // can be called before Zigbee is initialized
+ ZbLoadDump();
+ ResponseCmndDone();
+}
+
//
// Command `ZbScan`
// Run an energy scan
@@ -1339,7 +1450,7 @@ void CmndZbenroll(void) {
if ((XdrvMailbox.data_len) && (ArgC() > 1)) { // Process parameter entry
char argument[XdrvMailbox.data_len];
- Z_Device & device = zigbee_devices.parseDeviceFromName(ArgV(argument, 1), nullptr, XdrvMailbox.payload);
+ Z_Device & device = zigbee_devices.parseDeviceFromName(ArgV(argument, 1), nullptr, nullptr, XdrvMailbox.payload);
int enrollEndpoint = atoi(ArgV(argument, 2));
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
@@ -1357,7 +1468,7 @@ void CmndZbcie(void) {
if ((XdrvMailbox.data_len) && (ArgC() > 1)) { // Process parameter entry
char argument[XdrvMailbox.data_len];
- Z_Device & device = zigbee_devices.parseDeviceFromName(ArgV(argument, 1), nullptr, XdrvMailbox.payload);
+ Z_Device & device = zigbee_devices.parseDeviceFromName(ArgV(argument, 1), nullptr, nullptr, XdrvMailbox.payload);
int enrollEndpoint = atoi(ArgV(argument, 2));
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
@@ -1379,7 +1490,7 @@ void CmndZbcie(void) {
// ZbRestore {"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
void CmndZbRestore(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- RemoveSpace(XdrvMailbox.data);
+ TrimSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) == 0) {
// if empty, log values for all devices
@@ -1425,7 +1536,7 @@ void CmndZbRestore(void) {
// do a sanity check, the first byte must equal the length of the buffer
if (buf.get8(0) == buf.len()) {
// good, we can hydrate
- hydrateSingleDevice(buf);
+ hydrateSingleDevice(buf, 4);
} else {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
@@ -1456,6 +1567,7 @@ void CmndZbPermitJoin(void) {
// ZNP Version
#ifdef USE_ZIGBEE_ZNP
+ // put all routers in pairing mode
SBuffer buf(34);
buf.add8(Z_SREQ | Z_ZDO); // 25
buf.add8(ZDO_MGMT_PERMIT_JOIN_REQ); // 36
@@ -1466,6 +1578,22 @@ void CmndZbPermitJoin(void) {
ZigbeeZNPSend(buf.getBuffer(), buf.len());
+ // send Green Power pairing mode
+ ZCLFrame zcl(4); // message is 4 bytes max
+
+ zcl.cmd = 0x02; // GP Proxy Commissioning Mode
+ zcl.cluster = 0x0021; // GP cluster
+ zcl.shortaddr = 0xFFFC; // Broadcast to all routers
+ zcl.srcendpoint = 0xF2; // GP endpoint
+ zcl.dstendpoint = 0xF2; // GP endpoint
+ zcl.needResponse = false; // as per GP spec The Disable default response sub-field of the Frame Control Field of the ZCL header shall be set to 0b1."
+ zcl.clusterSpecific = true; // command
+ zcl.direct = true; // broadcast so no need to discover routes
+ zcl.direction = true; // server to client
+ zcl.payload.add8(0x0B); // Action=1, gpsCommissioningExitMode=0b101 (window expiration + GP Proxy Commissioning Mode)
+ zcl.payload.add16(duration);
+ zigbeeZCLSendCmd(zcl);
+
#endif // USE_ZIGBEE_ZNP
// EZSP VERSION
@@ -1580,7 +1708,7 @@ void CmndZbStatus(void) {
if (0 == XdrvMailbox.index) {
dump = zigbee_devices.dumpCoordinator();
} else {
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload);
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload);
if (XdrvMailbox.data_len > 0) {
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
dump = zigbee_devices.dumpDevice(XdrvMailbox.index, device);
@@ -1599,7 +1727,7 @@ void CmndZbStatus(void) {
//
void CmndZbData(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- RemoveSpace(XdrvMailbox.data);
+ TrimSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) == 0) {
// if empty, log values for all devices
@@ -1612,7 +1740,7 @@ void CmndZbData(void) {
strtok_r(XdrvMailbox.data, ",", &p);
// parse first part,
- Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
+ Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered
if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; }
if (p) {
@@ -1644,7 +1772,7 @@ void CmndZbConfig(void) {
int8_t zb_txradio_dbm = Settings->zb_txradio_dbm;
// if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
- RemoveSpace(XdrvMailbox.data);
+ TrimSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) > 0) {
JsonParser parser(XdrvMailbox.data);
JsonParserObject root = parser.getRootObject();
@@ -1781,6 +1909,10 @@ const char ZB_WEB_U[] PROGMEM =
""
"\0"
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ //=ZB_WEB_GP
+ "GP"
+ "\0"
+ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//=ZB_WEB_LAST_SEEN
"🕗%02d%c"
"\0"
@@ -1820,16 +1952,17 @@ enum {
ZB_WEB_MAP_REFRESH=1164,
ZB_WEB_STATUS_LINE=1230,
ZB_WEB_BATTERY=1338,
- ZB_WEB_LAST_SEEN=1410,
- ZB_WEB_COLOR_RGB=1458,
- ZB_WEB_LINE_START=1518,
- ZB_WEB_LIGHT_CT=1558,
- ZB_WEB_END_STATUS=1613,
- ZB_WEB_LINE_END=1630,
+ ZB_WEB_GP=1410,
+ ZB_WEB_LAST_SEEN=1466,
+ ZB_WEB_COLOR_RGB=1514,
+ ZB_WEB_LINE_START=1574,
+ ZB_WEB_LIGHT_CT=1614,
+ ZB_WEB_END_STATUS=1669,
+ ZB_WEB_LINE_END=1686,
};
-// Compressed from 1649 to 1124, -31.8%
-const char ZB_WEB[] PROGMEM = "\x00\x68\x3D\x0E\xCA\xB1\xC1\x33\xF0\xF6\xD1\xEE\x3D\x3D\x46\x41\x33\xF0\xE8\x6D"
+// Compressed from 1705 to 1156, -32.2%
+const char ZB_WEB[] PROGMEM = "\x00\x6B\x3D\x0E\xCA\xB1\xC1\x33\xF0\xF6\xD1\xEE\x3D\x3D\x46\x41\x33\xF0\xE8\x6D"
"\xA1\x15\x08\x79\xF6\x51\xDD\x3C\xCC\x6F\xFD\x47\x58\x62\xB4\x21\x0E\xF1\xED\x1F"
"\xD1\x28\x51\xE6\x72\x99\x0C\x36\x1E\x0C\x67\x51\xD7\xED\x36\xB3\xCC\xE7\x99\xF4"
"\x7D\x1E\xE2\x04\x3C\x40\x2B\x04\x3C\x28\x10\xB0\x93\x99\xA4\x30\xD8\x08\x36\x8E"
@@ -1877,15 +2010,16 @@ const char ZB_WEB[] PROGMEM = "\x00\x68\x3D\x0E\xCA\xB1\xC1\x33\xF0\xF6\xD1\xEE\
"\x35\x16\xA3\xEB\xC7\xD8\x21\xE7\x1E\xD3\xEC\xFC\x9C\x2F\x9E\x9A\x08\x52\xCF\x60"
"\xEA\x3D\x80\x85\x82\x9E\xC3\xE8\x43\xE8\xFA\x3E\xBC\x08\x9D\x2A\x01\x03\xAC\xEB"
"\x1C\x11\xE6\x7D\x08\x30\xD8\x08\x7C\xFA\x1F\x47\x1D\x11\xB0\xFA\x38\xE8\x8D\x87"
- "\xD1\xC7\x44\x6C\x3D\x87\xE1\xE8\x76\x69\xF9\x38\x5F\x3D\x28\x40\x43\xC2\xC1\x43"
- "\x01\x3F\x47\x91\xB0\xE4\x22\x30\x73\x77\xC7\x83\xE9\xD1\x08\x7D\x07\x38\x5F\x40"
- "\x8D\xAA\x9B\x01\x1B\x46\x0C\x23\xCC\xF2\x3E\x8E\x3A\x22\x36\x1F\x47\x1D\x11\x1B"
- "\x0F\xA3\x8E\x88\x8D\x80\x83\x9D\x82\x44\xF0\x47\xE1\xF0\x10\xF8\x78\x41\xE0\x5E"
- "\x19\x7C\x7C\x3D\x87\x30\xF6\x1F\x87\xE8\xF2\x59\xEF\x9E\x0A\x70\xBE\x08\x5D\x17"
- "\x2A\x01\x42\xE0\xC4\x83\x2A\x2B\x47\xD0\x87\xB0\xFC\x3D\x3C\x36\xC2\x08\xFC\x3F"
- "\x47\x91\xC5\xF5\xF3\xC1\xDC\x3D\x0E\xC2\x04\x19\x87\xD0\x84\x68\x08\x5D\x18\x29"
- "\xC2\xF8\x21\x74\x1D\xCE\xCA\x10\xFC\x3E\xBC\x7B\x59\xEE\x9C\x2F\x82\x3F\x4E\xE8"
- "\x10\x79\x39\x9C\x2F\x9B";
+ "\xD1\xC7\x44\x6C\x3D\x87\xE1\xE8\x76\x69\xF9\x38\x5F\x04\x1E\x86\xD8\x21\x68\xA4"
+ "\x3D\xF6\xF9\x10\xCC\x1F\x7F\x3E\xC1\x2B\xA1\xDE\x73\x08\x8C\x1C\xC3\xC1\xF6\x7E"
+ "\x11\x0F\x10\xC0\x42\xE8\x77\xCE\x17\xCF\x4A\x10\x10\xF4\x30\x50\xCE\x4F\xD1\xE4"
+ "\x6C\x39\x08\x8C\x1C\xDD\xF1\xE0\xFA\x74\x42\x1F\x41\xCE\x17\xD0\x23\x70\x1A\x6C"
+ "\x04\x6D\xF8\x30\x8F\x33\xC8\xFA\x38\xE8\x88\xD8\x7D\x1C\x74\x44\x6C\x3E\x8E\x3A"
+ "\x22\x36\x02\x0E\xE6\x09\x13\xC1\x1F\x8B\x40\x43\xE2\xC1\x07\x81\x78\x65\xF1\xF0"
+ "\xF6\x1C\xC3\xD8\x7E\x1F\xA3\xC9\x67\xBE\x78\x29\xC2\xF8\x21\x74\x6A\x01\x0B\x86"
+ "\x92\x0C\xA9\x1F\x42\x1E\xC3\xF0\xF4\xF0\xDB\x08\x23\xF0\xFD\x1E\x47\x17\xD7\xCF"
+ "\x07\x70\xF4\x3B\x08\x10\x66\x1F\x42\x11\xA0\x22\x70\x4E\x08\x3D\x0A\xF3\xB2\x84"
+ "\x3F\x0F\xAF\x1E\xD6\x7B\xA7\x0B\xE0\x8F\xD3\xF2\x04\x1E\x5C\x67\x0B\xE6";
// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
@@ -1980,7 +2114,14 @@ const char HTTP_ZB_VERSION[] PROGMEM =
const char HTTP_BTN_ZB_BUTTONS[] PROGMEM =
""
""
- "";
+ ""
+ "";
+
+const char HTTP_BTN_ZB_BUTTONS_DISABLED[] PROGMEM =
+ ""
+ ""
+ ""
+ "";
void ZigbeeShow(bool json)
{
@@ -2045,6 +2186,8 @@ void ZigbeeShow(bool json)
changeUIntScale(device.batt_percent, 0, 100, 0, 14),
(color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF)
);
+ } else if (device.isGP()) { // display GP in green for Green Power
+ snprintf_P(sbatt, sizeof(sbatt), msg[ZB_WEB_GP]);
}
uint32_t num_bars = 0;
@@ -2154,10 +2297,12 @@ void ZigbeeShow(bool json)
if (plug_voltage || plug_power) {
WSContentSend_P(PSTR(" ⚡ "));
if (plug_voltage) {
- WSContentSend_P(PSTR(" %dV"), plug.getMainsVoltage());
+ float mains_voltage = plug.getMainsVoltage();
+ WSContentSend_P(PSTR(" %-1_fV"), &mains_voltage);
}
if (plug_power) {
- WSContentSend_P(PSTR(" %dW"), plug.getMainsPower());
+ float mains_power = plug.getMainsPower();
+ WSContentSend_P(PSTR(" %-1_fW"), &mains_power);
}
}
WSContentSend_P(PSTR("{e}"));
@@ -2176,6 +2321,10 @@ void ZigbeeShow(bool json)
WSContentSend_P(HTTP_ZB_VERSION,
zigbee.major_rel, zigbee.minor_rel,
zigbee.maint_rel, zigbee.revision);
+ WSContentSend_P(HTTP_BTN_ZB_BUTTONS);
+ } else {
+ uint32_t grey = WebColor(COL_FORM);
+ WSContentSend_P(HTTP_BTN_ZB_BUTTONS_DISABLED, grey, grey);
}
#endif
}
@@ -2267,9 +2416,6 @@ bool Xdrv23(uint8_t function) {
WebServer_on(PSTR("/zbm"), ZigbeeShowMap, HTTP_GET); // add web handler for Zigbee map
WebServer_on(PSTR("/zbr"), ZigbeeMapRefresh, HTTP_GET); // add web handler for Zigbee map refresh
break;
- case FUNC_WEB_ADD_MAIN_BUTTON:
- WSContentSend_P(HTTP_BTN_ZB_BUTTONS);
- break;
#endif // USE_WEBSERVER
case FUNC_PRE_INIT:
ZigbeeInit();
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_39_thermostat.ino b/tasmota/tasmota_xdrv_driver/xdrv_39_thermostat.ino
index 26620394a..4d341fd4d 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_39_thermostat.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_39_thermostat.ino
@@ -1236,7 +1236,7 @@ bool ThermostatTimerArm(uint8_t ctr_output, int16_t tempVal)
bool result = false;
// TempVal unit is tenths of degrees celsius
if ((tempVal >= -1000)
- && (tempVal <= 1000)
+ && (tempVal <= 2000)
&& (tempVal >= Thermostat[ctr_output].temp_frost_protect)) {
Thermostat[ctr_output].temp_target_level = tempVal;
Thermostat[ctr_output].status.thermostat_mode = THERMOSTAT_AUTOMATIC_OP;
@@ -1368,7 +1368,7 @@ void ThermostatGetLocalSensor(uint8_t ctr_output) {
value = ThermostatFahrenheitToCelsius(value, TEMP_CONV_ABSOLUTE);
}
if ( (value >= -1000)
- && (value <= 1000)
+ && (value <= 2000)
&& (Thermostat[ctr_output].status.sensor_type == SENSOR_LOCAL)) {
uint32_t timestamp = TasmotaGlobal.uptime;
// Calculate temperature gradient if temperature value has changed
@@ -1440,7 +1440,7 @@ void CmndTempFrostProtectSet(void)
value = (int16_t)(CharToFloat(XdrvMailbox.data) * 10);
}
if ( (value >= -1000)
- && (value <= 1000)) {
+ && (value <= 2000)) {
Thermostat[ctr_output].temp_frost_protect = value;
}
}
@@ -1571,7 +1571,7 @@ void CmndTempMeasuredSet(void)
value = (int16_t)(CharToFloat(XdrvMailbox.data) * 10);
}
if ( (value >= -1000)
- && (value <= 1000)
+ && (value <= 2000)
&& (Thermostat[ctr_output].status.sensor_type == SENSOR_MQTT)) {
uint32_t timestamp = TasmotaGlobal.uptime;
// Calculate temperature gradient if temperature value has changed
@@ -1609,7 +1609,7 @@ void CmndTempTargetSet(void)
value = (int16_t)(CharToFloat(XdrvMailbox.data) * 10);
}
if ( (value >= -1000)
- && (value <= 1000)
+ && (value <= 2000)
&& (value >= Thermostat[ctr_output].temp_frost_protect)) {
Thermostat[ctr_output].temp_target_level = value;
}
@@ -2061,7 +2061,7 @@ void ThermostatShow(uint8_t ctr_output, bool json)
int16_t value = Thermostat[ctr_output].temp_measured_gradient;
if (Thermostat[ctr_output].status.temp_format == TEMP_FAHRENHEIT) {
value = ThermostatCelsiusToFahrenheit((int32_t)Thermostat[ctr_output].temp_measured_gradient, TEMP_CONV_RELATIVE);
- }
+ }
f_temperature = value / 1000.0f;
WSContentSend_PD(HTTP_THERMOSTAT_TEMPERATURE, D_THERMOSTAT_GRADIENT, Settings->flag2.temperature_resolution, &f_temperature, c_unit);
WSContentSend_P(HTTP_THERMOSTAT_DUTY_CYCLE, ThermostatGetDutyCycle(ctr_output) );
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_MI32.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_MI32.ino
index e247ee01a..52f9c02e0 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_MI32.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_MI32.ino
@@ -36,80 +36,39 @@ extern "C" {
********************************************************************/
extern uint32_t MI32numberOfDevices();
- extern const char * MI32getDeviceName(uint32_t slot);
+ extern char * MI32getDeviceName(uint32_t slot);
extern void MI32setBatteryForSlot(uint32_t slot, uint8_t value);
extern void MI32setHumidityForSlot(uint32_t slot, float value);
extern void MI32setTemperatureForSlot(uint32_t slot, float value);
extern uint8_t * MI32getDeviceMAC(uint32_t slot);
- int be_MI32_devices(bvm *vm);
- int be_MI32_devices(bvm *vm) {
- uint32_t devices = MI32numberOfDevices();
- be_pushint(vm, devices);
- be_return(vm);
+ int be_MI32_devices(void) {
+ return MI32numberOfDevices();
}
- int be_MI32_set_bat(bvm *vm);
- int be_MI32_set_bat(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 3 && be_isint(vm, 2) && be_isint(vm, 3)) {
- uint32_t slot = be_toint(vm, 2);
- int32_t bat_val = be_toint(vm, 3);
- MI32setBatteryForSlot(slot,bat_val);
- be_return(vm); // Return
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_MI32_set_bat(int slot, int bat_val){
+ MI32setBatteryForSlot(slot,bat_val);
}
- int be_MI32_get_name(bvm *vm);
- int be_MI32_get_name(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 2 && be_isint(vm, 2)) {
- uint32_t slot = be_toint(vm, 2);
- const char * name = MI32getDeviceName(slot);
- be_pushstring(vm,name);
- be_return(vm); // Return
- }
- be_raise(vm, kTypeError, nullptr);
+ const char* be_MI32_get_name(int slot){
+ return MI32getDeviceName(slot);
}
- int be_MI32_get_MAC(bvm *vm);
- int be_MI32_get_MAC(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 2 && be_isint(vm, 2)) {
- uint32_t slot = be_toint(vm, 2);
- uint8_t *buffer = MI32getDeviceMAC(slot);
- size_t len = 6;
- if(buffer != NULL) {
- be_pushbytes(vm,buffer,len);
- be_return(vm); // Return
- }
+ uint8_t *be_MI32_get_MAC(int32_t slot, size_t *size){
+ *size = 6;
+ uint8_t * buffer = MI32getDeviceMAC(slot);
+ if(buffer == nullptr){
+ *size = 0;
}
- be_raise(vm, kTypeError, nullptr);
+ return buffer;
}
- int be_MI32_set_hum(bvm *vm);
- int be_MI32_set_hum(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 3 && be_isint(vm, 2) && be_isreal(vm, 3)) {
- uint32_t slot = be_toint(vm, 2);
- float hum_val = be_toreal(vm, 3);
- MI32setHumidityForSlot(slot,hum_val);
- be_return(vm); // Return
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_MI32_set_hum(int slot, int hum_val){
+ MI32setHumidityForSlot(slot,hum_val);
}
- int be_MI32_set_temp(bvm *vm);
- int be_MI32_set_temp(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 3 && be_isint(vm, 2) && be_isreal(vm, 3)) {
- uint32_t slot = be_toint(vm, 2);
- float temp_val = be_toreal(vm, 3);
- MI32setTemperatureForSlot(slot,temp_val);
- be_return(vm); // Return
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_MI32_set_temp(int slot, int temp_val){
+ MI32setTemperatureForSlot(slot,temp_val);
}
@@ -118,121 +77,107 @@ extern "C" {
********************************************************************/
extern void MI32setBerryAdvCB(void* function, uint8_t *buffer);
extern void MI32setBerryConnCB(void* function, uint8_t *buffer);
- extern bool MI32runBerryConnection(uint8_t operation, bool response);
- extern bool MI32setBerryCtxSvc(const char *Svc, bool discoverAttributes);
+ extern bool MI32runBerryConnection(uint8_t operation, bbool response);
+ extern bool MI32setBerryCtxSvc(const char *Svc, bbool discoverAttributes);
extern bool MI32setBerryCtxChr(const char *Chr);
extern bool MI32setBerryCtxMAC(uint8_t *MAC, uint8_t type);
extern bool MI32addMACtoBlockList(uint8_t *MAC, uint8_t type);
extern bool MI32addMACtoWatchList(uint8_t *MAC, uint8_t type);
- int be_BLE_reg_conn_cb(bvm *vm);
- int be_BLE_reg_conn_cb(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 3 && be_iscomptr(vm, 2)) {
- void* cb = be_tocomptr(vm, 2);
- size_t len;
- uint8_t * buf = (uint8_t*)be_tobytes(vm, 3, &len);
- MI32setBerryConnCB(cb,buf);
- be_return(vm);
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_BLE_reg_conn_cb(void* function, uint8_t *buffer);
+ void be_BLE_reg_conn_cb(void* function, uint8_t *buffer){
+ MI32setBerryConnCB(function,buffer);
}
- int be_BLE_reg_adv_cb(bvm *vm);
- int be_BLE_reg_adv_cb(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 3 && be_iscomptr(vm, 2)) {
- void* cb = be_tocomptr(vm, 2);
- size_t len;
- uint8_t * buf = (uint8_t*)be_tobytes(vm, 3, &len);
- MI32setBerryAdvCB(cb,buf);
- be_return(vm); // Return
+ void be_BLE_reg_adv_cb(void* function, uint8_t *buffer);
+ void be_BLE_reg_adv_cb(void* function, uint8_t *buffer){
+ if(function == 0){
+ MI32setBerryAdvCB(NULL,NULL);
}
- else if(argc == 2 && be_isint(vm, 2)){
- if(be_toint(vm, 2) == 0){
- MI32setBerryAdvCB(NULL,NULL);
- be_return(vm); // Return
- }
+ else if(buffer){
+ MI32setBerryAdvCB(function,buffer);
}
- be_raise(vm, kTypeError, nullptr);
}
- int be_BLE_set_MAC(bvm *vm);
- int be_BLE_set_MAC(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc > 1 && be_isbytes(vm, 2)) {
- size_t len = 6;
- uint8_t type = 0;
- if(argc == 3 && be_isint(vm, 3)){
- type = be_toint(vm,3);
- }
- if (MI32setBerryCtxMAC((uint8_t*)be_tobytes(vm, 2, &len),type)) be_return(vm);
+ bool be_BLE_MAC_size(struct bvm *vm, size_t size){
+ if(size != 6){
+ be_raisef(vm, "ble_error", "BLE: wrong size of MAC");
+ return false;
}
- be_raise(vm, kTypeError, nullptr);
+ return true;
}
- int be_BLE_set_service(bvm *vm);
- int be_BLE_set_service(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc > 1 && be_isstring(vm, 2)) {
- bool discoverAttributes = false;
- if(argc == 3 && be_isint(vm, 3)){
- discoverAttributes = be_toint(vm,3)>0;
- }
- if (MI32setBerryCtxSvc(be_tostring(vm, 2),discoverAttributes)) be_return(vm);
+ void be_BLE_set_MAC(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type);
+ void be_BLE_set_MAC(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type){
+ if(!be_BLE_MAC_size(vm, size)){
+ return;
}
- be_raise(vm, kTypeError, nullptr);
+ uint8_t _type = 0;
+ if(type){
+ _type = type;
+ }
+ if (MI32setBerryCtxMAC(buf,_type)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not set MAC");
}
- int be_BLE_set_characteristic(bvm *vm);
- int be_BLE_set_characteristic(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc == 2 && be_isstring(vm, 2)) {
- if (MI32setBerryCtxChr(be_tostring(vm, 2))) be_return(vm);
+ void be_BLE_set_service(struct bvm *vm, const char *Svc, bbool discoverAttributes);
+ void be_BLE_set_service(struct bvm *vm, const char *Svc, bbool discoverAttributes){
+ bool _discoverAttributes = false;
+ if(discoverAttributes){
+ _discoverAttributes = discoverAttributes ;
}
- be_raise(vm, kTypeError, nullptr);
+ if (MI32setBerryCtxSvc(Svc,_discoverAttributes)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not set service");
}
- int be_BLE_run(bvm *vm);
- int be_BLE_run(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if ((argc > 1) && be_isint(vm, 2)) {
- bool response = false;
- if(argc == 3 && be_isint(vm, 3)){
- response = be_toint(vm,3)>0;
- }
- if (MI32runBerryConnection(be_toint(vm, 2),response)) be_return(vm);
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_BLE_set_characteristic(struct bvm *vm, const char *Chr);
+ void be_BLE_set_characteristic(struct bvm *vm, const char *Chr){
+
+ if (MI32setBerryCtxChr(Chr)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not set characteristic");
}
- int be_BLE_adv_block(bvm *vm);
- int be_BLE_adv_block(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc > 1 && be_isbytes(vm, 2)) {
- size_t len = 6;
- uint8_t type = 0;
- if(argc == 3 && be_isint(vm, 3)){
- type = be_toint(vm,3);
- }
- if(MI32addMACtoBlockList((uint8_t*)be_tobytes(vm, 2, &len),type)) be_return(vm);
- }
- be_raise(vm, kTypeError, nullptr);
+ void be_BLE_run(struct bvm *vm, uint8_t operation, bbool response);
+ void be_BLE_run(struct bvm *vm, uint8_t operation, bbool response){
+ bool _response = false;
+ if(response){
+ _response = response;
+ }
+ if (MI32runBerryConnection(operation,_response)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not run operation");
}
- int be_BLE_adv_watch(bvm *vm);
- int be_BLE_adv_watch(bvm *vm){
- int32_t argc = be_top(vm); // Get the number of arguments
- if (argc > 1 && be_isbytes(vm, 2)) {
- size_t len = 6;
- uint8_t type = 0;
- if(argc == 3 && be_isint(vm, 3)){
- type = be_toint(vm,3);
+ void be_BLE_adv_block(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type);
+ void be_BLE_adv_block(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type){
+ if(!be_BLE_MAC_size(vm, size)){
+ return;
}
- if (MI32addMACtoWatchList((uint8_t*)be_tobytes(vm, 2, &len),type)) be_return(vm);
+ uint8_t _type = 0;
+ if(type){
+ _type = type;
+ }
+ if(MI32addMACtoBlockList(buf, _type)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not block MAC");
}
- be_raise(vm, kTypeError, nullptr);
+
+ void be_BLE_adv_watch(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type);
+ void be_BLE_adv_watch(struct bvm *vm, uint8_t *buf, size_t size, uint8_t type){
+ if(!be_BLE_MAC_size(vm, size)){
+ return;
+ }
+ uint8_t _type = 0;
+ if(type){
+ _type = type;
+ }
+ if(MI32addMACtoWatchList(buf, _type)) return;
+
+ be_raisef(vm, "ble_error", "BLE: could not add MAC to watch list");
}
} //extern "C"
@@ -247,7 +192,7 @@ BLE.set_svc
BLE.set_chr
BLE.set_MAC
-BLE.run(op)
+BLE.run(op, optional: bool response)
be_BLE_op:
1 read
2 write
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_lvgl.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_lvgl.ino
index 56aa6e833..e6ec58e56 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_lvgl.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_lvgl.ino
@@ -514,15 +514,14 @@ extern "C" {
// write BMP header
static const uint8_t bmp_sign[] = { 0x42, 0x4d }; // BM = Windows
f.write(bmp_sign, sizeof(bmp_sign));
- size_t bmp_size = bmp_width * bmp_height * LV_COLOR_DEPTH / 8 + 0x44;
+ size_t bmp_size = bmp_width * bmp_height * LV_COLOR_DEPTH / 8 + 0x42;
f.write((uint8_t*)&bmp_size, sizeof(bmp_size));
uint32_t zero = 0;
f.write((uint8_t*) &zero, sizeof(zero)); // reserved 4-bytes
- uint32_t bmp_offset_to_pixels = 0x44; // TODO
+ uint32_t bmp_offset_to_pixels = 0x42; // TODO
f.write((uint8_t*) &bmp_offset_to_pixels, sizeof(bmp_offset_to_pixels));
- // DIB Header BITMAPINFOHEADER
- size_t bmp_dib_header_size = 52; // BITMAPV2INFOHEADER
+ size_t bmp_dib_header_size = 0x28;
f.write((uint8_t*) &bmp_dib_header_size, sizeof(bmp_dib_header_size));
f.write((uint8_t*) &bmp_width, sizeof(bmp_width));
@@ -530,13 +529,15 @@ extern "C" {
// rest of header
// BITMAPV2INFOHEADER = 52 bytes header, 40 bytes sub-header
- static const uint8_t bmp_dib_header[] = {
+ static const uint8_t bmp_dib_header1[] = {
0x01, 0x00, // planes
16, 0x00, // bits per pixel = 16
0x03, 0x00, 0x00, 0x00, // compression = BI_BITFIELDS uncrompressed
- 0x00, 0x00, 0x00, 0x00, // Image size, 0 is valid for BI_RGB (uncompressed) TODO
- 0x00, 0x00, 0x00, 0x00, // X pixels per meter
- 0x00, 0x00, 0x00, 0x00, // Y pixels per meter
+ };
+
+ static const uint8_t bmp_dib_header2[] = {
+ 0xC4, 0xE0, 0x00, 0x00, // X pixels per meter
+ 0xC4, 0xE0, 0x00, 0x00, // Y pixels per meter
0x00, 0x00, 0x00, 0x00, // Colors in table
0x00, 0x00, 0x00, 0x00, // Important color count
@@ -544,10 +545,10 @@ extern "C" {
0x00, 0xF8, 0x00, 0x00, // Red channel mask
0xE0, 0x07, 0x00, 0x00, // Green channel mask
0x1F, 0x00, 0x00, 0x00, // Blue channel mask
-
- 0x00, 0x00, // Padding to align on 4 bytes boundary
};
- f.write(bmp_dib_header, sizeof(bmp_dib_header));
+ f.write(bmp_dib_header1, sizeof(bmp_dib_header1));
+ f.write((uint8_t*)&bmp_size, sizeof(bmp_size));
+ f.write(bmp_dib_header2, sizeof(bmp_dib_header2));
// now we can write the pixels array
// redraw screen
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_mqtt.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_mqtt.ino
index 77525039c..16139a295 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_mqtt.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_mqtt.ino
@@ -25,54 +25,46 @@
// Berry: `tasmota.publish(topic, payload [, retain:bool, start:int, len:int]) -> nil``
// is_method is true if called from Tasmota class, false if called from mqtt module
-int32_t be_mqtt_publish(struct bvm *vm, bool is_method) {
- int32_t top = be_top(vm); // Get the number of arguments
- if (top >= 2+is_method && be_isstring(vm, 1+is_method) && (be_isstring(vm, 2+is_method) || be_isbytes(vm, 2+is_method))) { // 2 mandatory string arguments
- bool retain = false;
- int32_t payload_start = 0;
- int32_t len = -1; // send all of it
- if (top >= 3+is_method) { retain = be_tobool(vm, 3+is_method); }
- if (top >= 4+is_method) {
- payload_start = be_toint(vm, 4+is_method);
- if (payload_start < 0) payload_start = 0;
- }
- if (top >= 5+is_method) { len = be_toint(vm, 5+is_method); }
- const char * topic = be_tostring(vm, 1+is_method);
- const char * payload = nullptr;
- size_t payload_len = 0;
-
- if (be_isstring(vm, 2+is_method)) {
- payload = be_tostring(vm, 2+is_method);
- payload_len = strlen(payload);
- } else {
- payload = (const char *) be_tobytes(vm, 2+is_method, &payload_len);
- }
- if (!payload) { be_raise(vm, "value_error", "Empty payload"); }
-
- // adjust start and len
- if (payload_start >= payload_len) { len = 0; } // send empty packet
- else if (len < 0) { len = payload_len - payload_start; } // send all packet, adjust len
- else if (payload_start + len > payload_len) { len = payload_len - payload_start; } // len is too long, adjust
- // adjust start
- payload = payload + payload_start;
-
- be_pop(vm, be_top(vm)); // clear stack to avoid any indirect warning message in subsequent calls to Berry
-
- MqttPublishPayload(topic, payload, len, retain);
-
- be_return_nil(vm); // Return
- }
- be_raise(vm, kTypeError, nullptr);
-}
-
extern "C" {
- int32_t l_publish(struct bvm *vm);
- int32_t l_publish(struct bvm *vm) {
- return be_mqtt_publish(vm, true);
- }
-
+ int32_t be_mqtt_publish(struct bvm *vm);
int32_t be_mqtt_publish(struct bvm *vm) {
- return be_mqtt_publish(vm, false);
+ int32_t top = be_top(vm); // Get the number of arguments
+ if (top >= 3 && be_isstring(vm, 2) && (be_isstring(vm, 3) || be_isbytes(vm, 3))) { // 2 mandatory string arguments
+ bool retain = false;
+ int32_t payload_start = 0;
+ int32_t len = -1; // send all of it
+ if (top >= 4) { retain = be_tobool(vm, 4); }
+ if (top >= 5) {
+ payload_start = be_toint(vm, 5);
+ if (payload_start < 0) payload_start = 0;
+ }
+ if (top >= 6) { len = be_toint(vm, 6); }
+ const char * topic = be_tostring(vm, 2);
+ const char * payload = nullptr;
+ size_t payload_len = 0;
+
+ if (be_isstring(vm, 3)) {
+ payload = be_tostring(vm, 3);
+ payload_len = strlen(payload);
+ } else {
+ payload = (const char *) be_tobytes(vm, 3, &payload_len);
+ }
+ if (!payload) { be_raise(vm, "value_error", "Empty payload"); }
+
+ // adjust start and len
+ if (payload_start >= payload_len) { len = 0; } // send empty packet
+ else if (len < 0) { len = payload_len - payload_start; } // send all packet, adjust len
+ else if (payload_start + len > payload_len) { len = payload_len - payload_start; } // len is too long, adjust
+ // adjust start
+ payload = payload + payload_start;
+
+ be_pop(vm, be_top(vm)); // clear stack to avoid any indirect warning message in subsequent calls to Berry
+
+ MqttPublishPayload(topic, payload, len, retain);
+
+ be_return_nil(vm); // Return
+ }
+ be_raise(vm, kTypeError, nullptr);
}
void be_mqtt_subscribe(const char* topic) {
@@ -84,6 +76,10 @@ extern "C" {
if (!topic) { return; }
MqttUnsubscribe(topic);
}
+
+ bbool be_mqtt_connected(void) {
+ return Mqtt.connected;
+ }
}
#endif // USE_BERRY
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino
index 6fddb151a..31206442d 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino
@@ -182,8 +182,8 @@ extern "C" {
int32_t top = be_top(vm); // Get the number of arguments
if (top == 1) { // no argument (instance only)
be_newobject(vm, "map");
- be_map_insert_int(vm, "flash", ESP.getFlashChipSize() / 1024);
- be_map_insert_int(vm, "flash_real", ESP_getFlashChipRealSize() / 1024);
+ be_map_insert_int(vm, "flash", ESP_getFlashChipMagicSize() / 1024);
+ be_map_insert_int(vm, "flash_real", ESP.getFlashChipSize() / 1024);
be_map_insert_int(vm, "program", ESP_getSketchSize() / 1024);
be_map_insert_int(vm, "program_free", ESP_getFreeSketchSpace() / 1024);
be_map_insert_int(vm, "heap_free", ESP_getFreeHeap() / 1024);
@@ -265,6 +265,7 @@ extern "C" {
be_map_insert_int(vm, "min", t->tm_min);
be_map_insert_int(vm, "sec", t->tm_sec);
be_map_insert_int(vm, "weekday", t->tm_wday);
+ be_map_insert_int(vm, "epoch", mktime(t));
if (unparsed) be_map_insert_str(vm, "unparsed", unparsed);
be_pop(vm, 1);
}
@@ -400,6 +401,74 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
+ // Find for an operator in the string
+ // takes a string, an offset to start the search from, and works in 2 modes.
+ // mode1 (false): loog for the first char of an operato
+ // mode2 (true): finds the last char of the operator
+ int32_t tasm_find_op(bvm *vm);
+ int32_t tasm_find_op(bvm *vm) {
+ int32_t top = be_top(vm); // Get the number of arguments
+ bool second_phase = false;
+ int32_t ret = -1;
+ if (top >= 2 && be_isstring(vm, 2)) {
+ const char *c = be_tostring(vm, 2);
+ if (top >= 3) {
+ second_phase = be_tobool(vm, 3);
+ }
+
+ if (!second_phase) {
+ int32_t idx = 0;
+ // search for `=`, `==`, `!=`, `!==`, `<`, `<=`, `>`, `>=`
+ while (*c && ret < 0) {
+ switch (c[0]) {
+ case '=':
+ case '<':
+ case '>':
+ ret = idx;
+ break; // anything starting with `=`, `<` or `>` is a valid operator
+ case '!':
+ if (c[1] == '=') {
+ ret = idx; // needs to start with `!=`
+ }
+ break;
+ default:
+ break;
+ }
+ c++;
+ idx++;
+ }
+ } else {
+ // second phase
+ switch (c[0]) {
+ case '<':
+ case '>':
+ case '=':
+ if (c[1] != '=') { ret = 1; } // `<` or `>` or `=`
+ else { ret = 2; } // `<=` or `>=` or `==`
+ break;
+ case '!':
+ if (c[1] != '=') { ; } // this is invalid if isolated `!`
+ if (c[2] != '=') { ret = 2; } // `!=`
+ else { ret = 3; } // `!==`
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ be_pushint(vm, ret);
+ be_return(vm);
+ }
+ /*
+
+ # test patterns
+ assert(tasmota._find_op("aaa#bbc==23",false) == 7)
+ assert(tasmota._find_op("==23",true) == 2)
+ assert(tasmota._find_op(">23",true) == 1)
+ assert(tasmota._find_op("aaa#bbc!23",false) == -1)
+
+ */
+
// web append with decimal conversion
int32_t l_webSend(bvm *vm);
int32_t l_webSend(bvm *vm) {
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino
index 37f506c0d..de58aeb91 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino
@@ -69,7 +69,7 @@ extern "C" {
return d->batt_percent == 255 ? -1 : d->batt_percent;
}
int32_t zd_battery_lastseen(const class Z_Device* d) {
- return 0; // TODO not yet known
+ return d->batt_last_seen; // TODO not yet known
}
}
@@ -166,8 +166,8 @@ extern "C" {
be_return(vm);
}
- int32_t callBerryZigbeeDispatcher(const char* cmd, const char* type, void* data, int32_t idx);
- int32_t callBerryZigbeeDispatcher(const char* cmd, const char* type, void* data, int32_t idx) {
+ int32_t callBerryZigbeeDispatcher(const char* event, const class ZCLFrame* zcl_frame, const class Z_attribute_list* attr_list, int32_t idx);
+ int32_t callBerryZigbeeDispatcher(const char* event, const class ZCLFrame* zcl_frame, const class Z_attribute_list* attr_list, int32_t idx) {
int32_t ret = 0;
bvm *vm = berry.vm;
@@ -178,9 +178,9 @@ extern "C" {
be_getmethod(vm, -1, PSTR("dispatch")); // method dispatch
if (!be_isnil(vm, -1)) {
be_pushvalue(vm, -2); // add instance as first arg
- be_pushstring(vm, cmd != nullptr ? cmd : "");
- be_pushstring(vm, type != nullptr ? type : "");
- be_pushcomptr(vm, data);
+ be_pushstring(vm, event != nullptr ? event : "");
+ be_pushcomptr(vm, (void*) zcl_frame);
+ be_pushcomptr(vm, (void*) attr_list);
be_pushint(vm, idx);
BrTimeoutStart();
ret = be_pcall(vm, 5); // 5 arguments
@@ -205,7 +205,7 @@ extern "C" {
}
/*********************************************************************************************\
- * Mapping for zcl_message
+ * Mapping for zcl_frame_ntv
*
\*********************************************************************************************/
extern "C" {
@@ -234,6 +234,53 @@ extern "C" {
#pragma GCC diagnostic pop
}
+/*********************************************************************************************\
+ * Mapping for zcl_attribute_ntv
+ *
+\*********************************************************************************************/
+extern "C" {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof" // avoid warnings since we're using offsetof() in a risky way
+
+ extern const be_ctypes_structure_t be_zigbee_zcl_attribute_list_struct = {
+ sizeof(Z_attribute_list), /* size in bytes */
+ 3, /* number of elements */
+ nullptr,
+ (const be_ctypes_structure_item_t[3]) {
+ { "_groupaddr", offsetof(Z_attribute_list, group_id), 0, 0, ctypes_u16, 0 },
+ { "_lqi", offsetof(Z_attribute_list, lqi), 0, 0, ctypes_u8, 0 },
+ { "_src_ep", offsetof(Z_attribute_list, src_ep), 0, 0, ctypes_u8, 0 },
+ }};
+
+ extern const be_ctypes_structure_t be_zigbee_zcl_attribute_struct = {
+ sizeof(Z_attribute), /* size in bytes */
+ 10, /* number of elements */
+ nullptr,
+ (const be_ctypes_structure_item_t[10]) {
+ { "_attr_id", offsetof(Z_attribute, attr_id), 0, 0, ctypes_u16, 0 },
+ { "_cluster", offsetof(Z_attribute, cluster), 0, 0, ctypes_u16, 0 },
+ { "_cmd", offsetof(Z_attribute, attr_id), 0, 0, ctypes_u8, 0 }, // low 8 bits of attr_id
+ { "_cmd_general", offsetof(Z_attribute, attr_id) + 1, 1, 1, ctypes_u8, 0 }, // bit #1 of byte+1
+ { "_direction", offsetof(Z_attribute, attr_id) + 1, 0, 1, ctypes_u8, 0 }, // bit #0 of byte+1
+ { "_iscmd", offsetof(Z_attribute, key_is_cmd), 0, 0, ctypes_u8, 0 },
+ { "attr_multiplier", offsetof(Z_attribute, attr_multiplier), 0, 0, ctypes_i8, 0 },
+ { "attr_divider", offsetof(Z_attribute, attr_divider), 0, 0, ctypes_i8, 0 },
+ { "attr_type", offsetof(Z_attribute, attr_type), 0, 0, ctypes_u8, 0 },
+ // { "key", offsetof(Z_attribute, key), 0, 0, ctypes_ptr32, 0 },
+ // { "key_is_pmem", offsetof(Z_attribute, key_is_pmem), 0, 0, ctypes_u8, 0 },
+ // { "key_is_str", offsetof(Z_attribute, key_is_str), 0, 0, ctypes_u8, 0 },
+ { "key_suffix", offsetof(Z_attribute, key_suffix), 0, 0, ctypes_u8, 0 },
+ // { "type", offsetof(Z_attribute, type), 0, 0, ctypes_u8, 0 },
+ // { "val_float", offsetof(Z_attribute, val), 0, 0, ctypes_float, 0 },
+ // { "val_i32", offsetof(Z_attribute, val), 0, 0, ctypes_i32, 0 },
+ // { "val_str_raw", offsetof(Z_attribute, val_str_raw), 0, 0, ctypes_u8, 0 },
+ // { "val_ptr", offsetof(Z_attribute, val), 0, 0, ctypes_ptr32, 0 },
+ // { "val_u32", offsetof(Z_attribute, val), 0, 0, ctypes_u32, 0 },
+ }};
+
+#pragma GCC diagnostic pop
+}
+
/*********************************************************************************************\
* Functions for zcl_frame
*
@@ -258,5 +305,295 @@ extern "C" {
}
}
+/*********************************************************************************************\
+ * Functions for zcl_attribute
+ *
+\*********************************************************************************************/
+extern "C" {
+ extern const bclass be_class_zcl_attribute_list;
+ extern const bclass be_class_zcl_attribute;
+ extern const bclass be_class_zcl_attribute_ntv;
+
+ void zat_zcl_attribute(struct bvm *vm, const Z_attribute *attr);
+
+ // Pushes the Z_attribute_list on the stack as a simple list
+ // Adds the output on top of stack and does not change rest of stack (stack size incremented by 1)
+ void zat_zcl_attribute_list_inner(struct bvm *vm, const Z_attribute_list* attrlist) {
+ be_newobject(vm, "list");
+
+ for (const auto & attr : *attrlist) {
+ zat_zcl_attribute(vm, &attr);
+ be_data_push(vm, -2);
+ be_pop(vm, 1);
+ }
+ be_pop(vm, 1);
+ }
+
+ // Pushes the Z_attribute on the stack as `zcl_attribute_ntv`
+ void zat_zcl_attribute(struct bvm *vm, const Z_attribute *attr) {
+ be_pushntvclass(vm, &be_class_zcl_attribute);
+ be_pushcomptr(vm, (void*) attr);
+
+ // instantiate
+ be_call(vm, 1); // 1 parameter
+ be_pop(vm, 1);
+ }
+
+ // Get typed value from zcl_attributes
+ int be_zigbee_zcl_attribute_ntv_get_val(struct bvm *vm) {
+ const Z_attribute *attr = (const Z_attribute*) be_tobytes(vm, 1, NULL);
+ // value
+ switch (attr->type) {
+ case Za_type::Za_bool:
+ be_pushbool(vm, attr->val.uval32 ? btrue : bfalse);
+ break;
+ case Za_type::Za_uint:
+ case Za_type::Za_int:
+ be_pushint(vm, attr->val.ival32);
+ break;
+ case Za_type::Za_float:
+ be_pushreal(vm, (breal)attr->val.fval);
+ break;
+ case Za_type::Za_raw:
+ be_pushbytes(vm, attr->val.bval->getBuffer(), attr->val.bval->len());
+ break;
+ case Za_type::Za_str:
+ be_pushstring(vm, attr->val.sval);
+ break;
+
+ case Za_type::Za_obj:
+ zat_zcl_attribute_list_inner(vm, attr->val.objval);
+ break;
+
+ case Za_type::Za_arr:
+ // json_format = true;
+ if (attr->val.arrval) {
+ String arrval = attr->val.arrval->toString();
+ be_module_load(vm, be_newstr(vm, "json"));
+ be_getmember(vm, -1, "load");
+ be_remove(vm, -2); // remove module 'json'
+ be_pushstring(vm, arrval.c_str());
+ be_call(vm, 1);
+ be_pop(vm, 1);
+ } else {
+ // push empty list
+ be_newobject(vm, "list");
+ be_pop(vm, 1);
+ }
+ break;
+
+ case Za_type::Za_none:
+ default:
+ be_pushnil(vm);
+ break;
+ }
+
+ be_return(vm);
+ }
+
+ // Initialize the Z_attribute_list memory zone with provided address
+ int be_zigbee_zcl_attribute_list_ntv_init(struct bvm *vm) {
+ size_t len;
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, &len);
+ attr_list = new(attr_list) Z_attribute_list(); // "placement new" to provide a fixed address https://isocpp.org/wiki/faq/dtors#placement-new
+ be_return_nil(vm);
+ }
+
+ // Deinitialize the Z_attribute_list memory zone with provided address
+ int be_zigbee_zcl_attribute_list_ntv_deinit(struct bvm *vm) {
+ size_t len;
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, &len);
+ if (attr_list) {
+ attr_list->~Z_attribute_list();
+ }
+ be_return_nil(vm);
+ }
+
+ // Size
+ int be_zigbee_zcl_attribute_list_ntv_size(struct bvm *vm) {
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, nullptr);
+ be_pushint(vm, attr_list->length());
+ be_return(vm);
+ }
+
+ // Item
+ int be_zigbee_zcl_attribute_list_ntv_item(struct bvm *vm) {
+ int32_t argc = be_top(vm);
+ if (argc >= 2) {
+ int32_t idx = be_toint(vm, 2);
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, nullptr);
+ const Z_attribute* attr = attr_list->at(idx);
+ if (attr) {
+ zat_zcl_attribute(vm, attr);
+ be_return(vm);
+ }
+ }
+ be_return_nil(vm);
+ }
+
+ // new_head
+ int be_zigbee_zcl_attribute_list_ntv_new_head(struct bvm *vm) {
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, nullptr);
+ Z_attribute &attr = attr_list->addHead();
+ zat_zcl_attribute(vm, &attr);
+ be_return(vm);
+ }
+
+ // new_tail
+ int be_zigbee_zcl_attribute_list_ntv_new_tail(struct bvm *vm) {
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, nullptr);
+ Z_attribute &attr = attr_list->addToLast();
+ zat_zcl_attribute(vm, &attr);
+ be_return(vm);
+ }
+
+ // Remove
+ int be_zigbee_zcl_attribute_list_ntv_remove(struct bvm *vm) {
+ int32_t argc = be_top(vm);
+ if (argc >= 2) {
+ int32_t idx = be_toint(vm, 2);
+ Z_attribute_list *attr_list = (Z_attribute_list*) be_tobytes(vm, 1, nullptr);
+ const Z_attribute* attr = attr_list->at(idx);
+ if (attr) {
+ attr_list->remove(attr);
+ }
+ }
+ be_return_nil(vm);
+ }
+
+ // Initialize the Z_attribute memory zone with provided address
+ int be_zigbee_zcl_attribute_ntv_init(struct bvm *vm) {
+ size_t len;
+ Z_attribute *attr = (Z_attribute*) be_tobytes(vm, 1, &len);
+ attr = new(attr) Z_attribute(); // "placement new" to provide a fixed address https://isocpp.org/wiki/faq/dtors#placement-new
+ be_return_nil(vm);
+ }
+
+ // Deinitialize the Z_attribute memory zone with provided address
+ int be_zigbee_zcl_attribute_ntv_deinit(struct bvm *vm) {
+ size_t len;
+ Z_attribute *attr = (Z_attribute*) be_tobytes(vm, 1, &len);
+ if (attr) {
+ attr->~Z_attribute();
+ }
+ be_return_nil(vm);
+ }
+
+ // Set typed value from zcl_attributes
+ int be_zigbee_zcl_attribute_ntv_set_val(struct bvm *vm) {
+ int32_t argc = be_top(vm);
+ if (argc >= 2) {
+ Z_attribute *attr = (Z_attribute*) be_tobytes(vm, 1, NULL);
+
+ if (be_isnil(vm, 2)) {
+ attr->setNone();
+ } else if (be_isbool(vm, 2)) {
+ attr->setBool(be_tobool(vm, 2));
+ } else if (be_isint(vm, 2)) {
+ attr->setInt(be_toint(vm, 2));
+ } else if (be_isreal(vm, 2)) {
+ attr->setFloat(be_toreal(vm, 2));
+ } else if (be_isstring(vm, 2)) {
+ attr->setStr(be_tostring(vm, 2));
+ } else if (be_isbytes(vm, 2)) {
+ size_t len;
+ const void* buf = be_tobytes(vm, 2, &len);
+ attr->setRaw(buf, len);
+ }
+ }
+
+ be_return(vm);
+ }
+
+ // returns the key as string or `nil` if no string key. Suffix is not appended
+ int be_zigbee_zcl_attribute_ntv_get_key(struct bvm *vm) {
+ const Z_attribute *attr = (const Z_attribute*) be_tobytes(vm, 1, NULL);
+ if (attr->key_is_str) {
+ be_pushstring(vm, attr->key);
+ } else {
+ be_pushnil(vm);
+ }
+ be_return(vm);
+ }
+
+ // set string key, or remove if `nil` or no parameter
+ int be_zigbee_zcl_attribute_ntv_set_key(struct bvm *vm) {
+ Z_attribute *attr = (Z_attribute*) be_tobytes(vm, 1, NULL);
+ int32_t argc = be_top(vm);
+ if (argc >= 2 && be_isstring(vm, 2)) {
+ const char* key = be_tostring(vm, 2);
+ attr->setKeyName(key, false);
+ } else {
+ attr->setKeyId(attr->cluster, attr->attr_id);
+ }
+ be_return_nil(vm);
+ }
+}
+
+extern "C" {
+ int zigbee_test_attr(struct bvm *vm) {
+ int32_t mode = be_toint(vm, 2);
+ if (mode < 10) {
+ //
+ } else {
+ Z_attribute *a = new Z_attribute();
+ if (mode == 10) {
+ a->setKeyId(1111, 2222);
+ a->setUInt(1337);
+ } else if (mode == 11) {
+ a->setKeyName("super_attribute");
+ a->key_suffix = 2;
+ a->setFloat(3.14);
+ } else if (mode == 12) {
+ a->setKeyName("array");
+ a->newJsonArray();
+ a->val.arrval->add(-1);
+ a->val.arrval->addStr("foo");
+ a->val.arrval->addStr("bar");
+ a->val.arrval->addStr("bar\"baz\'toto");
+ } else if (mode == 13) {
+ a->setKeyName("list");
+ a->newAttrList();
+ Z_attribute &subattr1 = a->val.objval->addAttribute(10,20);
+ subattr1.setStr("sub1");
+ Z_attribute &subattr2 = a->val.objval->addAttribute(11,21);
+ subattr2.setStr("sub2");
+ }
+ zat_zcl_attribute(vm, a);
+ }
+ be_return(vm);
+ }
+
+
+ // Creates a zcl_attributes from Z_attribute_list
+ // Adds the output on top of stack and does not change rest of stack (stack size incremented by 1)
+ void zat_zcl_attribute_list(struct bvm *vm, uint16_t shortaddr, const Z_attribute_list* attr_list) {
+ be_pushntvclass(vm, &be_class_zcl_attribute_list);
+ be_pushcomptr(vm, (void*) attr_list);
+ // // instantiate
+ be_call(vm, 1); // 1 parameter
+ be_pop(vm, 1);
+
+ if (shortaddr != BAD_SHORTADDR) {
+ be_pushint(vm, shortaddr);
+ be_setmember(vm, -2, "shortaddr");
+ be_pop(vm, 1);
+ }
+ }
+
+ int zigbee_test_msg(struct bvm *vm) {
+ Z_attribute_list attr_list;
+
+ attr_list.lqi = 250;
+ Z_attribute &subattr1 = attr_list.addAttribute(10,20);
+ subattr1.setStr("sub1");
+ Z_attribute &subattr2 = attr_list.addAttribute(11,21);
+ subattr2.setStr("sub2");
+
+ zat_zcl_attribute_list(vm, 100, &attr_list);
+ be_return(vm);
+ }
+}
+
#endif // USE_ZIGBEE
#endif // USE_BERRY
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino
index e4c302bdb..36ac8b4e0 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino
@@ -230,6 +230,30 @@ void BerryObservability(bvm *vm, int event...) {
vm_usage, vm_usage2, vm_freed, vm_scanned, gc_elapsed,
slots_used_before_gc, slots_allocated_before_gc,
slots_used_after_gc, slots_allocated_after_gc);
+
+ // Add more in-deptch metrics
+ AddLog(LOG_LEVEL_DEBUG_MORE, D_LOG_BERRY "GC timing (us) 1:%i 2:%i 3:%i 4:%i 5:%i total:%i",
+ vm->micros_gc1 - vm->micros_gc0,
+ vm->micros_gc2 - vm->micros_gc1,
+ vm->micros_gc3 - vm->micros_gc2,
+ vm->micros_gc4 - vm->micros_gc3,
+ vm->micros_gc5 - vm->micros_gc4,
+ vm->micros_gc5 - vm->micros_gc0
+ );
+ AddLog(LOG_LEVEL_DEBUG_MORE, D_LOG_BERRY "GC by type "
+ "string:%i class:%i proto:%i instance:%i map:%i "
+ "list:%i closure:%i ntvclos:%i module:%i comobj:%i",
+ vm->gc_mark_string,
+ vm->gc_mark_class,
+ vm->gc_mark_proto,
+ vm->gc_mark_instance,
+ vm->gc_mark_map,
+ vm->gc_mark_list,
+ vm->gc_mark_closure,
+ vm->gc_mark_ntvclos,
+ vm->gc_mark_module,
+ vm->gc_mark_comobj
+ );
// make new threshold tighter when we reach high memory usage
if (!UsePSRAM() && vm->gc.threshold > 20*1024) {
vm->gc.threshold = vm->gc.usage + 10*1024; // increase by only 10 KB
@@ -287,10 +311,15 @@ void BerryInit(void) {
do {
berry.vm = be_vm_new(); /* create a virtual machine instance */
be_set_obs_hook(berry.vm, &BerryObservability); /* attach observability hook */
+ be_set_obs_micros(berry.vm, (bmicrosfnct)µs);
comp_set_named_gbl(berry.vm); /* Enable named globals in Berry compiler */
comp_set_strict(berry.vm); /* Enable strict mode in Berry compiler, equivalent of `import strict` */
be_set_ctype_func_hanlder(berry.vm, be_call_ctype_func);
+ if (UsePSRAM()) { // if PSRAM is available, raise the max size to 512kb
+ berry.vm->bytesmaxsize = 512 * 1024;
+ }
+
be_load_custom_libs(berry.vm); // load classes and modules
// Set the GC threshold to 3584 bytes to avoid the first useless GC
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_55_touch.ino b/tasmota/tasmota_xdrv_driver/xdrv_55_touch.ino
index 474460fc5..7ce4b1fed 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_55_touch.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_55_touch.ino
@@ -1,5 +1,5 @@
/*
- xdrv_55_touch.ino - Touch contolers
+ xdrv_55_touch.ino - Touch controllers
Copyright (C) 2021 Gerhard Mutz, Theo Arends & Stephan Hadinger
@@ -19,24 +19,24 @@
/*******************************************************************************************\
* Universal TouchScreen driver, extensible via Berry
- *
+ *
* API:
* void Touch_Init() - TODO
- *
+ *
* uint32_t Touch_Status(int32_t sel)
* 0: return 1 if TSGlobal.touched
* 1: return x
* 2: return y
- * -1: return raw x (before conersion for resistive)
+ * -1: return raw x (before conversion for resistive)
* -2: return raw y
- *
+ *
* void Touch_Check(void(*rotconvert)(int16_t *x, int16_t *y))
- *
+ *
* void TS_RotConvert(int16_t *x, int16_t *y) - calls the renderer's rotation converter
\*******************************************************************************************/
-#if defined(USE_LVGL_TOUCHSCREEN) || defined(USE_FT5206) || defined(USE_XPT2046) || defined(USE_LILYGO47) || defined(USE_TOUCH_BUTTONS)
+#if defined(USE_LVGL_TOUCHSCREEN) || defined(USE_FT5206) || defined(USE_XPT2046) || defined(USE_LILYGO47) || defined(USE_TOUCH_BUTTONS) || defined(SIMPLE_RES_TOUCH)
#ifdef USE_DISPLAY_LVGL_ONLY
#undef USE_TOUCH_BUTTONS
@@ -73,6 +73,7 @@ TSGlobal_t TSGlobal;
bool FT5206_found = false;
bool XPT2046_found = false;
+bool SRES_found = false;
#ifndef MAX_TOUCH_BUTTONS
#define MAX_TOUCH_BUTTONS 16
@@ -131,6 +132,58 @@ uint32_t Touch_Status(int32_t sel) {
uint8_t tbstate[3];
#endif // USE_M5STACK_CORE2
+
+// simple resistive touch pins
+// with dma it should check for active transfers
+// so currently dont use dma
+#ifdef SIMPLE_RES_TOUCH
+struct RES_TOUCH {
+ int8_t xplus;
+ int8_t xminus;
+ int8_t yplus;
+ int8_t yminus;
+ uint16_t xp;
+ uint16_t yp;
+} sres_touch;
+
+void Simple_ResTouch_Init(int8_t xp, int8_t xm, int8_t yp, int8_t ym) {
+ sres_touch.xplus = xp; // d1
+ sres_touch.xminus = xm; // cs
+ sres_touch.yplus = yp; // rs
+ sres_touch.yminus = ym; // d0
+ SRES_found = true;
+ AddLog(LOG_LEVEL_INFO, PSTR("TS: simple resistive touch init"));
+}
+
+#define SRES_THRESHOLD 500
+
+bool SRES_touched() {
+ uint32_t val = renderer->get_sr_touch(sres_touch.xplus, sres_touch.xminus, sres_touch.yplus, sres_touch.yminus);
+ if (val == 0) {
+ return false;
+ }
+ sres_touch.xp = val >> 16;
+ sres_touch.yp = val & 0xffff;
+
+ int16_t xp = sres_touch.xp;
+ int16_t yp = sres_touch.yp;
+
+ //AddLog(LOG_LEVEL_INFO, "TS x=%i y=%i)", xp, yp);
+
+ if (xp > SRES_THRESHOLD && yp > SRES_THRESHOLD) {
+ return 1;
+ }
+ return 0;
+}
+
+int16_t SRES_x() {
+ return sres_touch.xp;
+}
+int16_t SRES_y() {
+ return sres_touch.yp;
+}
+#endif
+
#ifdef USE_FT5206
#include
// touch panel controller
@@ -189,11 +242,20 @@ int16_t XPT2046_y() {
}
#endif // USE_XPT2046
-
-
void Touch_Check(void(*rotconvert)(int16_t *x, int16_t *y)) {
static bool was_touched = false; // flag used to log the data sent when the screen was just released
+
+#ifdef SIMPLE_RES_TOUCH
+ if (SRES_found) {
+ TSGlobal.touched = SRES_touched();
+ if (TSGlobal.touched) {
+ TSGlobal.raw_touch_xp = SRES_x();
+ TSGlobal.raw_touch_yp = SRES_y();
+ }
+ }
+#endif
+
#ifdef USE_FT5206
if (FT5206_found) {
TSGlobal.touched = FT5206_touched();
@@ -213,6 +275,7 @@ void Touch_Check(void(*rotconvert)(int16_t *x, int16_t *y)) {
}
}
#endif // USE_XPT2046
+
TSGlobal.touch_xp = TSGlobal.raw_touch_xp;
TSGlobal.touch_yp = TSGlobal.raw_touch_yp;
@@ -270,6 +333,8 @@ void Touch_Check(void(*rotconvert)(int16_t *x, int16_t *y)) {
}
}
+extern uint8_t GT911_found;
+
#ifdef USE_TOUCH_BUTTONS
void Touch_MQTT(uint8_t index, const char *cp, uint32_t val) {
#ifdef USE_FT5206
@@ -277,12 +342,20 @@ void Touch_MQTT(uint8_t index, const char *cp, uint32_t val) {
#endif
#ifdef USE_XPT2046
if (XPT2046_found) ResponseTime_P(PSTR(",\"XPT2046\":{\"%s%d\":\"%d\"}}"), cp, index+1, val);
+#endif // USE_XPT2046
+#ifdef USE_GT911
+ if (GT911_found) ResponseTime_P(PSTR(",\"GT911\":{\"%s%d\":\"%d\"}}"), cp, index+1, val);
#endif // USE_XPT2046
MqttPublishTeleSensor();
}
+void EP_Drawbutton(uint32_t count) {
+ renderer->ep_update_area(buttons[count]->spars.xp, buttons[count]->spars.yp, buttons[count]->spars.xs, buttons[count]->spars.ys, 3);
+}
+
void Touch_RDW_BUTT(uint32_t count, uint32_t pwr) {
buttons[count]->xdrawButton(pwr);
+ EP_Drawbutton(count);
if (pwr) buttons[count]->vpower.on_off = 1;
else buttons[count]->vpower.on_off = 0;
}
@@ -293,8 +366,8 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
uint8_t vbutt=0;
if (!renderer) return;
- if (TSGlobal.touched) {
- // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("touch after convert %d - %d"), pLoc.x, pLoc.y);
+ if (touched) {
+ //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("touch after convert %d - %d"), touch_x, touch_y);
// now must compare with defined buttons
for (uint8_t count = 0; count < MAX_TOUCH_BUTTONS; count++) {
if (buttons[count]) {
@@ -307,8 +380,8 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
if (!buttons[count]->vpower.is_virtual) {
uint8_t pwr=bitRead(TasmotaGlobal.power, rbutt);
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
- ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
Touch_RDW_BUTT(count, !pwr);
+ ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
}
} else {
// virtual button
@@ -323,7 +396,9 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
cp="PBT";
}
buttons[count]->xdrawButton(buttons[count]->vpower.on_off);
+ EP_Drawbutton(count);
Touch_MQTT(count, cp, buttons[count]->vpower.on_off);
+
}
}
}
@@ -337,6 +412,7 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
// slider
if (buttons[count]->didhit(touch_x, touch_y)) {
uint16_t value = buttons[count]->UpdateSlider(touch_x, touch_y);
+ EP_Drawbutton(count);
Touch_MQTT(count, "SLD", value);
}
}
@@ -356,6 +432,7 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
buttons[count]->vpower.on_off = 0;
Touch_MQTT(count,"PBT", buttons[count]->vpower.on_off);
buttons[count]->xdrawButton(buttons[count]->vpower.on_off);
+ EP_Drawbutton(count);
}
}
}
@@ -371,8 +448,6 @@ void CheckTouchButtons(bool touched, int16_t touch_x, int16_t touch_y) {
}
}
}
- TSGlobal.raw_touch_xp = TSGlobal.touch_xp = 0;
- TSGlobal.raw_touch_yp = TSGlobal.touch_yp = 0;
}
}
#endif // USE_TOUCH_BUTTONS
@@ -391,7 +466,7 @@ bool Xdrv55(uint8_t function) {
case FUNC_INIT:
break;
case FUNC_EVERY_100_MSECOND:
- if (FT5206_found || XPT2046_found) {
+ if (FT5206_found || XPT2046_found || SRES_found) {
Touch_Check(TS_RotConvert);
}
break;
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_59_influxdb.ino b/tasmota/tasmota_xdrv_driver/xdrv_59_influxdb.ino
index 48adb935c..d98011399 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_59_influxdb.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_59_influxdb.ino
@@ -373,7 +373,7 @@ void InfluxDbProcessJson(bool use_copy = false) {
}
void InfluxDbProcess(bool use_copy) {
- if (Settings->sbflag1.influxdb_sensor) {
+ if (Settings->sbflag1.influxdb_sensor) { // IfxSensor
InfluxDbProcessJson(use_copy);
}
}
@@ -476,9 +476,9 @@ void CmndInfluxDbState(void) {
void CmndInfluxDbSensor(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
- Settings->sbflag1.influxdb_sensor = XdrvMailbox.payload;
+ Settings->sbflag1.influxdb_sensor = XdrvMailbox.payload; // IfxSensor
}
- ResponseCmndStateText(Settings->sbflag1.influxdb_sensor);
+ ResponseCmndStateText(Settings->sbflag1.influxdb_sensor); // IfxSensor
}
void CmndInfluxDbLog(void) {
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino b/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino
index ac624d0fc..479ea560b 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino
@@ -26,12 +26,23 @@
* When USE_MODBUS_BRIDGE_TCP is also defined, this bridge can also be used as an ModbusTCP
* bridge.
*
- * Example Command:
+ * Example Commands:
+ * -- Read Coils --
+ * ModbusSend {"deviceaddress": 1, "functioncode": 1, "startaddress": 1, "type":"bit", "count":2}
+ *
* -- Read Input Register --
* ModbusSend {"deviceaddress": 1, "functioncode": 3, "startaddress": 1, "type":"uint16", "count":2}
- *
+ *
* -- Write multiple coils --
- * ModbusSend {"deviceaddress": 1, "functioncode": 15, "startaddress": 1, "type":"uint16", "count":4, "values":[1,2,3,4]}
+ * ModbusSend {"deviceaddress": 1, "functioncode": 15, "startaddress": 1, "type":"bit", "count":4, "values":[1,0,1,1]}
+ *
+ * Info for modbusBridgeTCPServer:
+ * https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/
+ *
+ * Info for modbus serial communications:
+ * https://ozeki.hu/p_5879-mobdbus-function-code-4-read-input-registers.html
+ * https://www.modbustools.com/modbus.html
+ * https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/
\*********************************************************************************************/
#define XDRV_63 63
@@ -110,7 +121,7 @@ enum class ModbusBridgeFunctionCode
{
mb_undefined = 0,
mb_readCoilStatus = 1,
- mb_readContactStatus = 2,
+ mb_readInputStatus = 2,
mb_readHoldingRegisters = 3,
mb_readInputRegisters = 4,
mb_writeSingleCoil = 5,
@@ -150,6 +161,7 @@ struct ModbusBridge
ModbusBridgeType type = ModbusBridgeType::mb_undefined;
uint16_t dataCount = 0;
+ uint16_t byteCount = 0;
uint16_t startAddress = 0;
uint8_t deviceAddress = 0;
uint8_t count = 0;
@@ -158,6 +170,24 @@ struct ModbusBridge
ModbusBridge modbusBridge;
+/********************************************************************************************/
+//
+// Helper functions for data conversion between little and big endian
+//
+uint16_t swap_endian16(uint16_t num)
+{
+ return (num>>8) | (num<<8);
+}
+
+uint32_t swap_endian32(uint32_t num)
+{
+ return ((num>>24)&0xff) | // move byte 3 to byte 0
+ ((num<<8)&0xff0000) | // move byte 1 to byte 2
+ ((num>>8)&0xff00) | // move byte 2 to byte 1
+ ((num<<24)&0xff000000); // byte 0 to byte 3
+}
+
+
/********************************************************************************************/
//
// Applies serial configuration to modbus serial port
@@ -216,8 +246,67 @@ void ModbusBridgeHandle(void)
if (data_ready)
{
uint8_t *buffer;
- buffer = (uint8_t *)malloc(9 + (modbusBridge.dataCount * 2)); // Addres(1), Function(1), Length(1), Data(1..n), CRC(2)
- uint32_t error = tasmotaModbus->ReceiveBuffer(buffer, modbusBridge.dataCount);
+ if (modbusBridge.byteCount == 0) modbusBridge.byteCount = modbusBridge.dataCount * 2;
+ buffer = (uint8_t *)malloc(9 + modbusBridge.byteCount); // Addres(1), Function(1), Length(1), Data(1..n), CRC(2)
+ memset(buffer, 0, 9 + modbusBridge.byteCount);
+ uint32_t error = tasmotaModbus->ReceiveBuffer(buffer, 0, modbusBridge.byteCount);
+
+#ifdef USE_MODBUS_BRIDGE_TCP
+ for (uint32_t i = 0; i < nitems(modbusBridgeTCP.client_tcp); i++)
+ {
+ WiFiClient &client = modbusBridgeTCP.client_tcp[i];
+ if (client)
+ {
+ uint8_t header[8];
+ uint8_t nrOfBytes = 8;
+ header[0] = modbusBridgeTCP.tcp_transaction_id >> 8;
+ header[1] = modbusBridgeTCP.tcp_transaction_id;
+ header[2] = 0;
+ header[3] = 0;
+ header[6] = buffer[0]; // Send slave address
+ header[7] = buffer[1]; // Send function code
+ if (error)
+ {
+ header[4] = 0; // Message Length Hi-Byte
+ header[5] = 3; // Message Length Low-Byte
+ header[7] = buffer[1] | 0x80; // Send function code
+ header[8] = error;
+ nrOfBytes += 1;
+ client.write(header, 9);
+ }
+ else if (buffer[1] <= 2)
+ {
+ header[4] = modbusBridge.byteCount >> 8;
+ header[5] = modbusBridge.byteCount + 3;
+ header[8] = modbusBridge.byteCount;
+ client.write(header, 9);
+ nrOfBytes += 1;
+ client.write(buffer + 3, modbusBridge.byteCount); // Don't send CRC
+ nrOfBytes += modbusBridge.byteCount;
+ }
+ else if (buffer[1] <= 4)
+ {
+ header[4] = modbusBridge.byteCount >> 8;
+ header[5] = modbusBridge.byteCount + 3;
+ header[8] = modbusBridge.byteCount;
+ client.write(header, 9);
+ nrOfBytes += 1;
+ client.write(buffer + 3, modbusBridge.byteCount); // Don't send CRC
+ nrOfBytes += modbusBridge.byteCount;
+ }
+ else
+ {
+ header[4] = 0; // Message Length Hi-Byte
+ header[5] = 6; // Message Length Low-Byte
+ client.write(header, 8);
+ client.write(buffer + 2, 4); // Don't send CRC
+ nrOfBytes += 4;
+ }
+ client.flush();
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBRTCP from Modbus deviceAddress %d, writing %d bytes to client"), buffer[0], nrOfBytes);
+ }
+ }
+#endif
if (error)
{
@@ -226,33 +315,9 @@ void ModbusBridgeHandle(void)
return;
}
-#ifdef USE_MODBUS_BRIDGE_TCP
- for (uint32_t i = 0; i < nitems(modbusBridgeTCP.client_tcp); i++)
- {
- WiFiClient &client = modbusBridgeTCP.client_tcp[i];
- if (client)
- {
- uint8_t MBAP_Header[7];
- MBAP_Header[0] = modbusBridgeTCP.tcp_transaction_id >> 8;
- MBAP_Header[1] = modbusBridgeTCP.tcp_transaction_id;
- MBAP_Header[2] = 0;
- MBAP_Header[3] = 0;
- MBAP_Header[4] = ((modbusBridge.dataCount * 2) + 3) >> 8;
- MBAP_Header[5] = (modbusBridge.dataCount * 2) + 3;
- MBAP_Header[6] = buffer[0]; // Send slave address
- client.write(MBAP_Header, 7);
- client.write(buffer + 1, 1); // Send Functioncode
- uint8_t bytecount[1];
- bytecount[0] = modbusBridge.dataCount * 2;
- client.write(bytecount, 1); // Send length of rtu data
- client.write(buffer + 3, (modbusBridge.dataCount * 2)); // Don't send CRC
- client.flush();
- AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBRTCP from Modbus deviceAddress %d, writing %d bytes to client"), buffer[0], (modbusBridge.dataCount * 2) + 9);
- }
- }
-#endif
-
+ modbusBridge.byteCount = 0;
ModbusBridgeError errorcode = ModbusBridgeError::noerror;
+
if (modbusBridge.deviceAddress == 0)
{
#ifdef USE_MODBUS_BRIDGE_TCP
@@ -278,17 +343,13 @@ void ModbusBridgeHandle(void)
}
else
{
- if ((modbusBridge.type == ModbusBridgeType::mb_int8 || modbusBridge.type == ModbusBridgeType::mb_uint8)
- && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
+ if ((modbusBridge.type == ModbusBridgeType::mb_int8 || modbusBridge.type == ModbusBridgeType::mb_uint8) && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
errorcode = ModbusBridgeError::wrongdataCount;
- else if ((modbusBridge.type == ModbusBridgeType::mb_bit)
- && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
+ else if ((modbusBridge.type == ModbusBridgeType::mb_bit) && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
errorcode = ModbusBridgeError::wrongdataCount;
- else if ((modbusBridge.type == ModbusBridgeType::mb_int16 || modbusBridge.type == ModbusBridgeType::mb_uint16)
- && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
+ else if ((modbusBridge.type == ModbusBridgeType::mb_int16 || modbusBridge.type == ModbusBridgeType::mb_uint16) && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
errorcode = ModbusBridgeError::wrongdataCount;
- else if ((modbusBridge.type == ModbusBridgeType::mb_int32 || modbusBridge.type == ModbusBridgeType::mb_uint32 || modbusBridge.type == ModbusBridgeType::mb_float)
- && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
+ else if ((modbusBridge.type == ModbusBridgeType::mb_int32 || modbusBridge.type == ModbusBridgeType::mb_uint32 || modbusBridge.type == ModbusBridgeType::mb_float) && ((uint8_t)modbusBridge.dataCount * 2 != (uint8_t)buffer[2]))
errorcode = ModbusBridgeError::wrongdataCount;
}
}
@@ -354,11 +415,13 @@ void ModbusBridgeHandle(void)
char svalue[MBR_MAX_VALUE_LENGTH + 1] = "";
if (modbusBridge.type == ModbusBridgeType::mb_float)
{
+ // Convert next 4 bytes to float
float value = 0;
if (buffer[1] < 3)
{
+ // In bit mode only convert returned bytes
if (buffer[2] - (count * 4))
- ((uint8_t *)&value)[0] = buffer[dataOffset + (count * 4)]; // Get int values
+ ((uint8_t *)&value)[0] = buffer[dataOffset + (count * 4)]; // Get float values
if ((buffer[2] - (count * 4)) >> 1)
((uint8_t *)&value)[1] = buffer[dataOffset + 1 + (count * 4)];
if ((buffer[2] - (count * 4) - 1) >> 1)
@@ -377,7 +440,17 @@ void ModbusBridgeHandle(void)
}
else if (modbusBridge.type == ModbusBridgeType::mb_bit)
{
- uint8_t value = (uint8_t)(buffer[dataOffset + (count >> 3)]);
+ uint8_t bits_left = modbusBridge.count - ((count/8) * 8);
+ uint8_t value = 0;
+ if (bits_left < 8)
+ {
+ uint8_t bits_skip = 8 - bits_left;
+ value = (uint8_t)(buffer[dataOffset + ((count + bits_skip) >> 3)]);
+ }
+ else
+ {
+ value = (uint8_t)(buffer[dataOffset + (count >> 3)]);
+ }
snprintf(svalue, MBR_MAX_VALUE_LENGTH, "%d", ((value >> (count & 7)) & 1));
}
else
@@ -389,17 +462,17 @@ void ModbusBridgeHandle(void)
if (buffer[1] < 3)
{
if (buffer[2] - (count * 4))
- ((uint8_t *)&value)[0] = buffer[dataOffset + (count * 4)]; // Get int values
- if ((buffer[2] - (count * 4)) >> 1)
+ ((uint8_t *)&value)[0] = buffer[dataOffset + (count * 4)]; // Get uint values
+ if (buffer[2] - ((count * 4) - 1))
((uint8_t *)&value)[1] = buffer[dataOffset + 1 + (count * 4)];
- if ((buffer[2] - (count * 4) - 1) >> 1)
+ if (buffer[2] - ((count * 4) - 2))
((uint8_t *)&value)[2] = buffer[dataOffset + 2 + (count * 4)];
- if ((buffer[2] - (count * 4)) >> 2)
+ if (buffer[2] - ((count * 4) - 3))
((uint8_t *)&value)[3] = buffer[dataOffset + 3 + (count * 4)];
}
else
{
- ((uint8_t *)&value)[3] = buffer[dataOffset + (count * 4)]; // Get int values
+ ((uint8_t *)&value)[3] = buffer[dataOffset + (count * 4)]; // Get uint values
((uint8_t *)&value)[2] = buffer[dataOffset + 1 + (count * 4)];
((uint8_t *)&value)[1] = buffer[dataOffset + 2 + (count * 4)];
((uint8_t *)&value)[0] = buffer[dataOffset + 3 + (count * 4)];
@@ -417,7 +490,7 @@ void ModbusBridgeHandle(void)
{
if (buffer[2] - (count * 2))
((uint8_t *)&value)[0] = buffer[dataOffset + (count * 2)];
- if ((buffer[2] - (count * 2)) >> 1)
+ if (buffer[2] - ((count * 2) - 1))
((uint8_t *)&value)[1] = buffer[dataOffset + 1 + (count * 2)];
}
else
@@ -439,12 +512,6 @@ void ModbusBridgeHandle(void)
else
snprintf(svalue, MBR_MAX_VALUE_LENGTH, "%u", value);
}
- /*
- else if (modbusBridge.type == ModbusBridgeType::mb_bit)
- {
- uint8_t value = (uint8_t)(buffer[dataOffset + count]);
- snprintf(svalue, MBR_MAX_VALUE_LENGTH, "%d%d%d%d%d%d%d%d", ((value >> 7) & 1), ((value >> 6) & 1), ((value >> 5) & 1), ((value >> 4) & 1), ((value >> 3) & 1), ((value >> 2) & 1), ((value >> 1) & 1), (value & 1));
- }*/
}
ResponseAppend_P(PSTR("%s"), svalue);
if (count < data_count - 1)
@@ -576,18 +643,60 @@ void ModbusTCPHandle(void)
busy = true;
}
}
- if (buf_len == 12)
+ if (buf_len >= 12)
{
uint8_t mbdeviceaddress = (uint8_t)modbusBridgeTCP.tcp_buf[6];
uint8_t mbfunctioncode = (uint8_t)modbusBridgeTCP.tcp_buf[7];
uint16_t mbstartaddress = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[8]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[9]));
- modbusBridge.dataCount = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[10]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[11]));
+ uint16_t *writeData = NULL;
+ uint16_t count = 0;
+
modbusBridgeTCP.tcp_transaction_id = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[0]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[1]));
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("MBS: MBRTCP to Modbus Transactionid:%d, deviceAddress:%d, functionCode:%d, startAddress:%d, Count:%d"),
- modbusBridgeTCP.tcp_transaction_id, mbdeviceaddress, mbfunctioncode, mbstartaddress, modbusBridge.dataCount);
+ if (mbfunctioncode <= 2)
+ {
+ count = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[10]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[11]));
+ modbusBridge.byteCount = ((count - 1) >> 3) + 1;
+ modbusBridge.dataCount = ((count - 1) >> 4) + 1;
+ }
+ else if (mbfunctioncode <= 4)
+ {
+ count = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[10]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[11]));
+ modbusBridge.byteCount = count * 2;
+ modbusBridge.dataCount = count;
+ }
+ else
+ {
+ // For functioncode 15 & 16 ignore bytecount, tasmotaModbus does calculate this
+ uint8_t dataStartByte = mbfunctioncode <= 6 ? 10 : 13;
+ uint16_t byteCount = (buf_len - dataStartByte);
+ modbusBridge.byteCount = 2;
+ modbusBridge.dataCount = 1;
- tasmotaModbus->Send(mbdeviceaddress, mbfunctioncode, mbstartaddress, modbusBridge.dataCount);
+ writeData = (uint16_t *)malloc((byteCount / 2)+1);
+
+ if ((mbfunctioncode == 15) || (mbfunctioncode == 16)) count = (uint16_t)((((uint16_t)modbusBridgeTCP.tcp_buf[10]) << 8) | ((uint16_t)modbusBridgeTCP.tcp_buf[11]));
+ else count = 1;
+
+ for (uint16_t dataPointer = 0; dataPointer < byteCount; dataPointer++)
+ {
+ if (dataPointer % 2 == 0)
+ {
+ writeData[dataPointer / 2] = (uint16_t)(((uint16_t)modbusBridgeTCP.tcp_buf[dataStartByte + dataPointer]) << 8);
+ }
+ else
+ {
+ writeData[dataPointer / 2] |= ((uint16_t)modbusBridgeTCP.tcp_buf[dataStartByte + dataPointer]);
+ }
+ }
+ }
+
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("MBS: MBRTCP to Modbus TransactionId:%d, deviceAddress:%d, functionCode:%d, startAddress:%d, count:%d, recvCount:%d, recvBytes:%d"),
+ modbusBridgeTCP.tcp_transaction_id, mbdeviceaddress, mbfunctioncode, mbstartaddress, count, modbusBridge.dataCount, modbusBridge.byteCount);
+
+ tasmotaModbus->Send(mbdeviceaddress, mbfunctioncode, mbstartaddress, count, writeData);
+
+ free(writeData);
}
}
yield(); // avoid WDT if heavy traffic
@@ -603,6 +712,7 @@ void CmndModbusBridgeSend(void)
{
uint16_t *writeData = NULL;
uint8_t writeDataSize = 0;
+ bool bitMode = false;
ModbusBridgeError errorcode = ModbusBridgeError::noerror;
JsonParser parser(XdrvMailbox.data);
@@ -615,7 +725,9 @@ void CmndModbusBridgeSend(void)
modbusBridge.startAddress = root.getULong(PSTR(D_JSON_MODBUS_START_ADDRESS), 0);
const char *stype = root.getStr(PSTR(D_JSON_MODBUS_TYPE), "uint8");
- modbusBridge.count = root.getUInt(PSTR(D_JSON_MODBUS_COUNT), 1);
+ modbusBridge.count = root.getUInt(PSTR(D_JSON_MODBUS_COUNT), 1); // Number of bits or bytes to read / write
+
+ if ((functionCode == 1) || (functionCode == 2) || (functionCode == 15)) bitMode = true;
if (modbusBridge.deviceAddress == 0)
errorcode = ModbusBridgeError::wrongdeviceaddress;
@@ -634,7 +746,8 @@ void CmndModbusBridgeSend(void)
if (strcmp(stype, "int8") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_int8;
- modbusBridge.dataCount = ((modbusBridge.count - 1)/ 2) + 1;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : ((modbusBridge.count - 1) / 2) + 1;
+ if (bitMode) modbusBridge.byteCount = (modbusBridge.count / 8) + 1;
}
else if (strcmp(stype, "int16") == 0)
{
@@ -644,12 +757,12 @@ void CmndModbusBridgeSend(void)
else if (strcmp(stype, "int32") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_int32;
- modbusBridge.dataCount = 2 * modbusBridge.count;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : 2 * modbusBridge.count;
}
else if ((strcmp(stype, "uint8") == 0))
{
modbusBridge.type = ModbusBridgeType::mb_uint8;
- modbusBridge.dataCount = ((modbusBridge.count - 1)/ 2) + 1;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : ((modbusBridge.count - 1) / 2) + 1;
}
else if ((strcmp(stype, "uint16") == 0) || (strcmp(stype, "") == 0)) // Default is uint16
{
@@ -659,37 +772,86 @@ void CmndModbusBridgeSend(void)
else if (strcmp(stype, "uint32") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_uint32;
- modbusBridge.dataCount = 2 * modbusBridge.count;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : 2 * modbusBridge.count;
}
else if (strcmp(stype, "float") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_float;
- modbusBridge.dataCount = 2 * modbusBridge.count;
+ modbusBridge.dataCount = bitMode ? 2 * modbusBridge.count : modbusBridge.count;
}
else if (strcmp(stype, "raw") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_raw;
- modbusBridge.dataCount = modbusBridge.count;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : ((modbusBridge.count - 1) / 2) + 1;
}
else if (strcmp(stype, "hex") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_hex;
- modbusBridge.dataCount = modbusBridge.count;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : ((modbusBridge.count - 1) / 2) + 1;
}
else if (strcmp(stype, "bit") == 0)
{
modbusBridge.type = ModbusBridgeType::mb_bit;
- modbusBridge.dataCount = ((modbusBridge.count - 1) / 16) + 1;
+ modbusBridge.dataCount = bitMode ? modbusBridge.count : ((modbusBridge.count - 1) / 16) + 1 ;
}
else
errorcode = ModbusBridgeError::wrongtype;
- if (modbusBridge.dataCount > MBR_MAX_REGISTERS)
+ // If functioncode is 15, the count is not the number of registers but the number
+ // of bit to write, so calculate the number data bytes to write.
+ if (modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeMultipleCoils)
+ {
+ modbusBridge.dataCount = modbusBridge.count;
+ }
+
+ // Prevent buffer overflow due to usage of to many registers
+ if ((!bitMode) && (modbusBridge.dataCount > MBR_MAX_REGISTERS))
errorcode = ModbusBridgeError::wrongcount;
- // If write data is specified in JSON copy it into writeData array
+ if ((bitMode) && (modbusBridge.dataCount > MBR_MAX_REGISTERS * 8))
+ errorcode = ModbusBridgeError::wrongcount;
+
+ // Get Json data for writing
JsonParserArray jsonDataArray = root[PSTR(D_JSON_MODBUS_VALUES)].getArray();
- if (jsonDataArray.isArray())
+ writeDataSize = jsonDataArray.size();
+
+ // Check if number of supplied data items is valid
+ switch (modbusBridge.functionCode)
+ {
+ case ModbusBridgeFunctionCode::mb_writeMultipleCoils:
+ // In writeMultipleCoil mode the amount of given data bits is less or equal to the count
+ switch (modbusBridge.type)
+ {
+ case ModbusBridgeType::mb_bit:
+ if (modbusBridge.count > writeDataSize) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ case ModbusBridgeType::mb_uint8:
+ case ModbusBridgeType::mb_int8:
+ case ModbusBridgeType::mb_raw:
+ case ModbusBridgeType::mb_hex:
+ if (modbusBridge.count > writeDataSize * 8) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ case ModbusBridgeType::mb_uint16:
+ case ModbusBridgeType::mb_int16:
+ if (modbusBridge.count > writeDataSize * 16) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ case ModbusBridgeType::mb_uint32:
+ case ModbusBridgeType::mb_int32:
+ if (modbusBridge.count > writeDataSize * 32) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ }
+ break;
+ case ModbusBridgeFunctionCode::mb_writeSingleRegister:
+ case ModbusBridgeFunctionCode::mb_writeSingleCoil:
+ if (modbusBridge.count != 1) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ case ModbusBridgeFunctionCode::mb_writeMultipleRegisters:
+ if (modbusBridge.count != writeDataSize) errorcode = ModbusBridgeError::wrongcount;
+ break;
+ }
+
+ // If write data is specified in JSON copy it into writeData array
+ if ((errorcode == ModbusBridgeError::noerror) && (jsonDataArray.isArray()))
{
if (modbusBridge.dataCount > 40)
{
@@ -697,73 +859,85 @@ void CmndModbusBridgeSend(void)
}
else
{
- writeDataSize = jsonDataArray.size();
+ writeData = (uint16_t *)malloc(modbusBridge.dataCount);
- if (modbusBridge.count != writeDataSize)
+ for (uint8_t jsonDataArrayPointer = 0; jsonDataArrayPointer < writeDataSize; jsonDataArrayPointer++)
{
- errorcode = ModbusBridgeError::wrongcount;
- }
- else
- {
- writeData = (uint16_t *)malloc(writeDataSize * 2);
- for (uint8_t jsonDataArrayPointer = 0; jsonDataArrayPointer < writeDataSize; jsonDataArrayPointer++)
+ if (errorcode != ModbusBridgeError::noerror) break;
+ switch (modbusBridge.type)
{
- switch (modbusBridge.type)
+ case ModbusBridgeType::mb_bit:
+ {
+ // Initialize current data/register to 0
+ if (jsonDataArrayPointer % 16 == 0)
{
- case ModbusBridgeType::mb_bit:
- {
- // Set 2 following bytes to 0
- if (jsonDataArrayPointer % 16 == 0)
- {
- writeData[jsonDataArrayPointer/15] = 0;
- }
- // Swap low and high bytes according to modbus specification
- uint16_t bitValue = (jsonDataArray[jsonDataArrayPointer].getUInt(0) == 1) ? 1 : 0;
- uint8_t bitPointer = (jsonDataArrayPointer % 15) + 8;
- if (bitPointer > 15) bitPointer -= 16;
-
- writeData[jsonDataArrayPointer/15] += bitValue << bitPointer;
- }
- break;
- case ModbusBridgeType::mb_int8:
- if (jsonDataArrayPointer % 2) writeData[jsonDataArrayPointer / 2] += (int8_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
- else writeData[jsonDataArrayPointer] = (int8_t)jsonDataArray[jsonDataArrayPointer / 2].getInt(0) << 8;
- break;
- case ModbusBridgeType::mb_uint8:
- if (jsonDataArrayPointer % 2) writeData[jsonDataArrayPointer / 2] += (uint8_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
- else writeData[jsonDataArrayPointer / 2] = (uint8_t)jsonDataArray[jsonDataArrayPointer].getInt(0) << 8;
- break;
- case ModbusBridgeType::mb_int16:
- writeData[jsonDataArrayPointer] = (int16_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
- break;
- case ModbusBridgeType::mb_uint16:
- writeData[jsonDataArrayPointer] = (uint16_t)jsonDataArray[jsonDataArrayPointer].getUInt(0);
- break;
- case ModbusBridgeType::mb_float:
- // TODO
- errorcode = ModbusBridgeError::wrongtype;
- break;
- case ModbusBridgeType::mb_int32:
- writeData[jsonDataArrayPointer++] = (int16_t)(jsonDataArray[jsonDataArrayPointer].getInt(0) >> 16);
- writeData[jsonDataArrayPointer] = (uint16_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
- break;
- case ModbusBridgeType::mb_uint32:
- writeData[jsonDataArrayPointer++] = (uint16_t)(jsonDataArray[jsonDataArrayPointer].getUInt(0) >> 16);
- writeData[jsonDataArrayPointer] = (uint16_t)jsonDataArray[jsonDataArrayPointer].getUInt(0);
- break;
- case ModbusBridgeType::mb_raw:
- writeData[jsonDataArrayPointer] = (uint8_t)jsonDataArray[jsonDataArrayPointer*2].getUInt(0) << 8 + (uint8_t)jsonDataArray[(jsonDataArrayPointer*2)+1].getUInt(0);
- break;
- case ModbusBridgeType::mb_hex:
- writeData[jsonDataArrayPointer] = (uint8_t)jsonDataArray[jsonDataArrayPointer*2].getUInt(0) << 8 + (uint8_t)jsonDataArray[(jsonDataArrayPointer*2)+1].getUInt(0);
- break;
- default:
- errorcode = ModbusBridgeError::wrongtype;
- break;
+ writeData[jsonDataArrayPointer / 15] = 0;
}
+ // Swap low and high bytes according to modbus specification
+ uint16_t bitValue = (jsonDataArray[jsonDataArrayPointer].getUInt(0) == 1) ? 1 : 0;
+ uint8_t bitPointer = (jsonDataArrayPointer % 16) + 8;
+ if (bitPointer > 15) bitPointer -= 16;
+
+ writeData[jsonDataArrayPointer / 16] += bitValue << bitPointer;
+ }
+ break;
+
+ case ModbusBridgeType::mb_int8:
+ if (jsonDataArrayPointer % 2)
+ writeData[jsonDataArrayPointer / 2] += (int8_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
+ else
+ writeData[jsonDataArrayPointer / 2] = (int8_t)jsonDataArray[jsonDataArrayPointer / 2].getInt(0) << 8;
+ if (modbusBridge.dataCount != writeDataSize / 2) errorcode = ModbusBridgeError::wrongcount;
+ break;
+
+ case ModbusBridgeType::mb_hex:
+ case ModbusBridgeType::mb_raw:
+ case ModbusBridgeType::mb_uint8:
+ if (jsonDataArrayPointer % 2)
+ writeData[jsonDataArrayPointer / 2] += (uint8_t)jsonDataArray[jsonDataArrayPointer].getUInt(0);
+ else
+ writeData[jsonDataArrayPointer / 2] = (uint8_t)jsonDataArray[jsonDataArrayPointer].getUInt(0) << 8;
+ if (modbusBridge.dataCount != writeDataSize / 2) errorcode = ModbusBridgeError::wrongcount;
+ break;
+
+ case ModbusBridgeType::mb_int16:
+ writeData[jsonDataArrayPointer] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getInt(0))
+ : (int16_t)jsonDataArray[jsonDataArrayPointer].getInt(0);
+ break;
+
+ case ModbusBridgeType::mb_uint16:
+ writeData[jsonDataArrayPointer] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getUInt(0))
+ : (int16_t)jsonDataArray[jsonDataArrayPointer].getUInt(0);
+ break;
+
+ case ModbusBridgeType::mb_int32:
+ writeData[(jsonDataArrayPointer * 2)] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getInt(0))
+ : (int16_t)(jsonDataArray[jsonDataArrayPointer].getInt(0) >> 16);
+ writeData[(jsonDataArrayPointer * 2) + 1] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getInt(0) >> 16)
+ : (uint16_t)(jsonDataArray[jsonDataArrayPointer].getInt(0));
+ break;
+
+ case ModbusBridgeType::mb_uint32:
+ writeData[(jsonDataArrayPointer * 2)] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getUInt(0))
+ : (uint16_t)(jsonDataArray[jsonDataArrayPointer].getUInt(0) >> 16);
+ writeData[(jsonDataArrayPointer * 2) + 1] = bitMode ? swap_endian16(jsonDataArray[jsonDataArrayPointer].getUInt(0) >> 16)
+ : (uint16_t)(jsonDataArray[jsonDataArrayPointer].getUInt(0));
+ break;
+
+ case ModbusBridgeType::mb_float:
+ // TODO
+ default:
+ errorcode = ModbusBridgeError::wrongtype;
+ break;
}
}
}
+
+ // Adapt data according to modbus protocol
+ if (modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleCoil)
+ {
+ writeData[0] = writeData[0] ? 0xFF00 : 0x0000; // High Byte
+ }
}
// Handle errorcode and exit function when an error has occured
@@ -774,23 +948,18 @@ void CmndModbusBridgeSend(void)
return;
}
- // Adapt data according to modbus protocol
- for (uint8_t writeDataPointer = 0; writeDataPointer < modbusBridge.dataCount; writeDataPointer++)
- {
- // For function code 5, on and off are 0xFF00 and 0x0000, not 1 and 0
- if (modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleCoil)
- {
- writeData[writeDataPointer] = writeData[writeDataPointer] ? 0xFF00 : 0x0000;
- }
- }
-
// If writing a single coil or single register, the register count is always 1. We also prevent writing data out of range
if ((modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleCoil) || (modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleRegister))
modbusBridge.dataCount = 1;
uint8_t error = tasmotaModbus->Send(modbusBridge.deviceAddress, (uint8_t)modbusBridge.functionCode, modbusBridge.startAddress, modbusBridge.dataCount, writeData);
- if (error) AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBR Driver send error %u"), error);
free(writeData);
+
+ if (error)
+ {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBR Driver send error %u"), error);
+ return;
+ }
ResponseCmndDone();
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino b/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino
index b49f781d8..d03bcb6da 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino
@@ -92,7 +92,7 @@
*
* Only boards with PSRAM should be used.
* To speed up cam processing cpu frequency should be better set to 240Mhz
- *
+ *
* remarks for AI-THINKER
* GPIO0 zero must be disconnected from any wire after programming because this pin drives the cam clock and does
* not tolerate any capictive load
@@ -109,33 +109,13 @@
#include "esp_camera.h"
#include "sensor.h"
#include "fb_gfx.h"
+#include "camera_pins.h"
bool HttpCheckPriviledgedAccess(bool);
extern ESP8266WebServer *Webserver;
#define BOUNDARY "e8b8c539-047d-4777-a985-fbba6edff11e"
-
-
-// CAMERA_MODEL_AI_THINKER default template pins
-#define PWDN_GPIO_NUM 32
-#define RESET_GPIO_NUM -1
-#define XCLK_GPIO_NUM 0
-#define SIOD_GPIO_NUM 26
-#define SIOC_GPIO_NUM 27
-
-#define Y9_GPIO_NUM 35
-#define Y8_GPIO_NUM 34
-#define Y7_GPIO_NUM 39
-#define Y6_GPIO_NUM 36
-#define Y5_GPIO_NUM 21
-#define Y4_GPIO_NUM 19
-#define Y3_GPIO_NUM 18
-#define Y2_GPIO_NUM 5
-#define VSYNC_GPIO_NUM 25
-#define HREF_GPIO_NUM 23
-#define PCLK_GPIO_NUM 22
-
#ifndef MAX_PICSTORE
#define MAX_PICSTORE 4
#endif
@@ -365,10 +345,9 @@ uint32_t WcSetup(int32_t fsiz) {
config.pin_sscb_scl = Pin(GPIO_WEBCAM_SIOC); // SIOC_GPIO_NUM;
config.pin_pwdn = Pin(GPIO_WEBCAM_PWDN); // PWDN_GPIO_NUM;
config.pin_reset = Pin(GPIO_WEBCAM_RESET); // RESET_GPIO_NUM;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: User template"));
- } else {
- // defaults to AI THINKER
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Template pin config"));
+ } else if (Y2_GPIO_NUM != -1) {
+ // Modell is set in camera_pins.h
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
@@ -385,20 +364,24 @@ uint32_t WcSetup(int32_t fsiz) {
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
- AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Default template"));
- }
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Compile flag pin config"));
+ } else {
+ // no valid config found -> abort
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: No pin config"));
+ return 0;
+}
int32_t ledc_channel = analogAttach(config.pin_xclk);
if (ledc_channel < 0) {
- AddLog(LOG_LEVEL_ERROR, "CAM: cannot allocated ledc cahnnel, remove a PWM GPIO");
+ AddLog(LOG_LEVEL_ERROR, "CAM: cannot allocated ledc channel, remove a PWM GPIO");
}
config.ledc_channel = (ledc_channel_t) ledc_channel;
AddLog(LOG_LEVEL_DEBUG_MORE, "CAM: XCLK on GPIO %i using ledc channel %i", config.pin_xclk, config.ledc_channel);
config.ledc_timer = LEDC_TIMER_0;
- config.xclk_freq_hz = 20000000;
+// config.xclk_freq_hz = 20000000;
+ config.xclk_freq_hz = Settings->webcam_clk * 1000000;
config.pixel_format = PIXFORMAT_JPEG;
-
//esp_log_level_set("*", ESP_LOG_INFO);
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
@@ -418,15 +401,7 @@ uint32_t WcSetup(int32_t fsiz) {
AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: PSRAM not found"));
}
-// AddLog(LOG_LEVEL_INFO, PSTR("CAM: heap check 1: %d"),ESP_getFreeHeap());
-
- // stupid workaround camera diver eats up static ram should prefer PSRAM
- // so we steal static ram to force driver to alloc PSRAM
-// uint32_t maxfram = ESP.getMaxAllocHeap();
-// void *x=malloc(maxfram-4096);
- void *x = 0;
esp_err_t err = esp_camera_init(&config);
- if (x) { free(x); }
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("CAM: Init failed with error 0x%x"), err);
@@ -1118,6 +1093,7 @@ void WcInit(void) {
#define D_CMND_RTSP "Rtsp"
#define D_CMND_WC_AUTH "Auth"
+#define D_CMND_WC_CLK "Clock"
const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix
"|" D_CMND_WC_STREAM "|" D_CMND_WC_RESOLUTION "|" D_CMND_WC_MIRROR "|" D_CMND_WC_FLIP "|"
@@ -1126,7 +1102,7 @@ const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix
D_CMND_WC_AEC_VALUE "|" D_CMND_WC_AE_LEVEL "|" D_CMND_WC_AEC2 "|" D_CMND_WC_AGC "|"
D_CMND_WC_AGC_GAIN "|" D_CMND_WC_GAINCEILING "|" D_CMND_WC_RAW_GMA "|" D_CMND_WC_LENC "|"
D_CMND_WC_WPC "|" D_CMND_WC_DCW "|" D_CMND_WC_BPC "|" D_CMND_WC_COLORBAR "|" D_CMND_WC_FEATURE "|"
- D_CMND_WC_SETDEFAULTS "|" D_CMND_WC_STATS "|" D_CMND_WC_INIT "|" D_CMND_WC_AUTH
+ D_CMND_WC_SETDEFAULTS "|" D_CMND_WC_STATS "|" D_CMND_WC_INIT "|" D_CMND_WC_AUTH "|" D_CMND_WC_CLK
#ifdef ENABLE_RTSPSERVER
"|" D_CMND_RTSP
#endif // ENABLE_RTSPSERVER
@@ -1139,7 +1115,7 @@ void (* const WCCommand[])(void) PROGMEM = {
&CmndWebcamAELevel, &CmndWebcamAEC2, &CmndWebcamAGC, &CmndWebcamAGCGain, &CmndWebcamGainCeiling,
&CmndWebcamGammaCorrect, &CmndWebcamLensCorrect, &CmndWebcamWPC, &CmndWebcamDCW, &CmndWebcamBPC,
&CmndWebcamColorbar, &CmndWebcamFeature, &CmndWebcamSetDefaults,
- &CmndWebcamStats, &CmndWebcamInit, &CmndWebcamAuth
+ &CmndWebcamStats, &CmndWebcamInit, &CmndWebcamAuth, &CmndWebcamClock
#ifdef ENABLE_RTSPSERVER
, &CmndWebRtsp
#endif // ENABLE_RTSPSERVER
@@ -1381,6 +1357,13 @@ void CmndWebcamAuth(void){
ResponseCmndNumber(Settings->webcam_config2.auth);
}
+void CmndWebcamClock(void){
+ if((XdrvMailbox.payload >= 10) && (XdrvMailbox.payload <= 200)){
+ Settings->webcam_clk = XdrvMailbox.payload;
+ }
+ ResponseCmndNumber(Settings->webcam_clk);
+}
+
void CmndWebcamInit(void) {
WcInterruptControl();
ResponseCmndDone();
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino
index b7cfada24..525e878cc 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino
@@ -154,6 +154,31 @@ void EthernetInit(void) {
int eth_power = Pin(GPIO_ETH_PHY_POWER);
int eth_mdc = Pin(GPIO_ETH_PHY_MDC);
int eth_mdio = Pin(GPIO_ETH_PHY_MDIO);
+#if CONFIG_IDF_TARGET_ESP32
+ // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII
+ gpio_reset_pin((gpio_num_t)GPIO_ETH_PHY_POWER);
+ gpio_reset_pin((gpio_num_t)GPIO_ETH_PHY_MDC);
+ gpio_reset_pin((gpio_num_t)GPIO_ETH_PHY_MDIO);
+ gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded
+ gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded
+ gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded
+ gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded
+ gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded
+ gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded
+ switch (Settings->eth_clk_mode) {
+ case 0: // ETH_CLOCK_GPIO0_IN
+ case 1: // ETH_CLOCK_GPIO0_OUT
+ gpio_reset_pin(GPIO_NUM_0);
+ break;
+ case 2: // ETH_CLOCK_GPIO16_OUT
+ gpio_reset_pin(GPIO_NUM_16);
+ break;
+ case 3: // ETH_CLOCK_GPIO17_OUT
+ gpio_reset_pin(GPIO_NUM_17);
+ break;
+ }
+ delay(1);
+#endif // CONFIG_IDF_TARGET_ESP32
if (!ETH.begin(Settings->eth_address, eth_power, eth_mdc, eth_mdio, (eth_phy_type_t)Settings->eth_type, (eth_clock_mode_t)Settings->eth_clk_mode)) {
AddLog(LOG_LEVEL_DEBUG, PSTR("ETH: Bad PHY type or init error"));
return;
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_83_esp32_watch.ino b/tasmota/tasmota_xdrv_driver/xdrv_83_esp32_watch.ino
index c4abb4825..14ce4eb12 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_83_esp32_watch.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_83_esp32_watch.ino
@@ -23,7 +23,6 @@
#include
#include
#include
-#include
#define XDRV_83 83
@@ -71,7 +70,7 @@ struct TTGO_globs {
void TTGO_Init(void) {
ttgo_globs.ttgo_power = new AXP20X_Class();
ttgo_globs.i2c = new I2CBus();
- initPower();
+ TTGO_initPower();
#ifdef USE_BMA423
ttgo_globs.bma = new BMA(*ttgo_globs.i2c);
@@ -119,7 +118,7 @@ void TTGO_Init(void) {
#endif // USE_BMA423
}
-void initPower(void) {
+void TTGO_initPower(void) {
int ret = ttgo_globs.ttgo_power->begin(axpReadBytes, axpWriteBytes);
if (ret == AXP_FAIL) {
//DBGX("AXP Power begin failed");
@@ -247,14 +246,14 @@ void TTGO_WebShow(uint32_t json) {
}
-void enableLDO3(bool en = true) {
+void TTGO_enableLDO3(bool en = true) {
if (!ttgo_globs.ttgo_power) return;
ttgo_globs.ttgo_power->setLDO3Mode(1);
ttgo_globs.ttgo_power->setPowerOutPut(AXP202_LDO3, en);
}
-void TTGO_audio_power(bool power) {
- enableLDO3(power);
+void TTGO_audio_power(bool power) { // Not every watch has audio
+ TTGO_enableLDO3(power);
}
const char TTGO_Commands[] PROGMEM = "TTGO|"
@@ -293,7 +292,7 @@ int32_t ttgo_sleeptime;
SettingsSaveAll();
RtcSettingsSave();
ttgo_globs.lenergy = true;
- rtc_clk_cpu_freq_set(RTC_CPU_FREQ_2M);
+ setCpuFrequencyMhz(10);
xEventGroupSetBits(ttgo_globs.isr_group, WATCH_FLAG_SLEEP_MODE);
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_HIGH_LEVEL);
@@ -319,7 +318,7 @@ int32_t ttgo_sleeptime;
if (ttgo_sleeptime) {
ttgo_globs.lenergy = false;
- rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);
+ setCpuFrequencyMhz(240);
#ifdef USE_DISPLAY
DisplayOnOff(1);
#endif
@@ -342,7 +341,7 @@ uint8_t data;
if (bits & WATCH_FLAG_SLEEP_EXIT) {
if (ttgo_globs.lenergy) {
ttgo_globs.lenergy = false;
- rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);
+ setCpuFrequencyMhz(240);
#ifdef USE_DISPLAY
DisplayOnOff(1);
#endif
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_86_esp32_sonoff_spm.ino b/tasmota/tasmota_xdrv_driver/xdrv_86_esp32_sonoff_spm.ino
index ca25ba979..aa1e2e153 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_86_esp32_sonoff_spm.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_86_esp32_sonoff_spm.ino
@@ -182,8 +182,6 @@
#define SSPM_TOTAL_MODULES 32 // Max number of SPM-4RELAY units for a total of 128 relays
-const uint32_t SSPM_VERSION = 0x0104; // Latest driver version (See settings deltas below)
-
enum SspmMachineStates { SPM_NONE, // Do nothing
SPM_WAIT, // Wait 100ms
SPM_RESET, // Toggle ARM reset pin
@@ -204,7 +202,7 @@ enum SspmMachineStates { SPM_NONE, // Do nothing
enum SspmDisplayModes { SPM_DISPLAY_ROTATE, SPM_DISPLAY_ROTATE_POWERED_ON, SPM_DISPLAY_TABS, SPM_DISPLAY_MAX_OPTION };
const char kSSPMTriggers[] PROGMEM = "Tasmota|Device|Overload|Overtemp";
-const char kSSPMOverload[] PROGMEM = "Tbd1|Voltage|Current|Power|Tbd2|Tbd3|Tbd4";
+const char kSSPMOverload[] PROGMEM = "Tbd1|Voltage|Current|Power|Surge|Tbd6|Tbd7";
#include
TasmotaSerial *SspmSerial;
@@ -307,79 +305,65 @@ TSspm *Sspm = nullptr;
* Driver Settings load and save using filesystem
\*********************************************************************************************/
-uint32_t SSPMSettingsCrc32(void) {
- // Use Tasmota CRC calculation function
- return GetCfgCrc32((uint8_t*)&Sspm->Settings +4, sizeof(tSspmSettings) -4); // Skip crc32
-}
-
-void SSPMSettingsDefault(void) {
- // Init default values in case file is not found
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM " D_USE_DEFAULTS));
+const uint32_t XDRV_86_VERSION = 0x0104; // Latest driver version (See settings deltas below)
+void Xdrv86SettingsLoad(void) {
+ // *** Start init default values in case file is not found ***
memset(&Sspm->Settings, 0x00, sizeof(tSspmSettings));
- Sspm->Settings.version = SSPM_VERSION;
+ Sspm->Settings.version = XDRV_86_VERSION;
+ // Init any other parameter in struct
Sspm->Settings.flag.display = SPM_DISPLAY_TABS;
- // Init any other parameter in struct SSPMSettings
-}
-void SSPMSettingsDelta(void) {
- // Fix possible setting deltas
- if (Sspm->Settings.version != SSPM_VERSION) { // Fix version dependent changes
- if (Sspm->Settings.version < 0x0104) {
- Sspm->Settings.flag.display = Settings->sbflag1.sspm_display;
- }
-
- // Set current version and save settings
- Sspm->Settings.version = SSPM_VERSION;
- SSPMSettingsSave();
- }
-}
-
-void SSPMSettingsLoad(void) {
- // Init default values in case file is not found
- SSPMSettingsDefault();
+ // *** End Init default values ***
+#ifndef USE_UFILESYS
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV86 Use defaults as file system not enabled"));
+#else
// Try to load file /.drvset086
char filename[20];
// Use for drivers:
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_86);
-
-#ifdef USE_UFILESYS
if (TfsLoadFile(filename, (uint8_t*)&Sspm->Settings, sizeof(tSspmSettings))) {
- // Fix possible setting deltas
- SSPMSettingsDelta();
+ if (Sspm->Settings.version != XDRV_86_VERSION) { // Fix version dependent changes
- AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM loaded from file"));
+ // *** Start fix possible setting deltas ***
+ if (Sspm->Settings.version < 0x0104) {
+ Sspm->Settings.flag.display = Settings->sbflag1.sspm_display;
+ }
+
+ // *** End setting deltas ***
+
+ // Set current version and save settings
+ Sspm->Settings.version = XDRV_86_VERSION;
+ Xdrv86SettingsSave();
+ }
+ AddLog(LOG_LEVEL_INFO, PSTR("CFG: XDRV86 loaded from file"));
} else {
// File system not ready: No flash space reserved for file system
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM ERROR File system not ready or file not found"));
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV86 Use defaults as file system not ready or file not found"));
}
-#else
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM ERROR File system not enabled"));
#endif // USE_UFILESYS
}
-void SSPMSettingsSave(void) {
+void Xdrv86SettingsSave(void) {
+#ifdef USE_UFILESYS
// Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
- if (SSPMSettingsCrc32() != Sspm->Settings.crc32) {
+ uint32_t crc32 = GetCfgCrc32((uint8_t*)&Sspm->Settings +4, sizeof(tSspmSettings) -4); // Skip crc32
+ if (crc32 != Sspm->Settings.crc32) {
// Try to save file /.drvset086
- Sspm->Settings.crc32 = SSPMSettingsCrc32();
+ Sspm->Settings.crc32 = crc32;
char filename[20];
// Use for drivers:
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_86);
-
-#ifdef USE_UFILESYS
if (TfsSaveFile(filename, (const uint8_t*)&Sspm->Settings, sizeof(tSspmSettings))) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM saved to file"));
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV86 saved to file"));
} else {
// File system not ready: No flash space reserved for file system
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM ERROR File system not ready or unable to save file"));
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV86 ERROR File system not ready or unable to save file"));
}
-#else
- AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: SPM ERROR File system not enabled"));
-#endif // USE_UFILESYS
}
+#endif // USE_UFILESYS
}
/*********************************************************************************************/
@@ -1475,10 +1459,12 @@ void SSPMHandleReceivedData(void) {
aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 00 00 00 00 00 00 00 05 e7 4b - At 02:50:30
aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 20 00 00 00 00 00 00 06 fe 09 - At 08:40:52
aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 00 00 00 00 00 00 00 07 26 ca - At 08:40:58
- Marker | |Ac|Cm|Size |St|Module id | |Vo|Cu|Po| | | |Ix|Chksm|
+ AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 14 00 25 6C 47 31 36 39 37 09 54 39 30 30 00 00 00 00 10 00 00 14 9A 66 - Response after L1 current surge overload powered off
+ Marker | |Ac|Cm|Size |St|Module id | |Vo|Cu|Po|Su| | |Ix|Chksm|
32..38 - Bitmask channel 01 = 1, 02 = 2, 04 = 3, 08 = 4 (Max border)
32..38 - Bitmask channel 10 = 1, 20 = 2, 40 = 3, 80 = 4 (Min border)
Cu - Current
+ Su - Current Surge or Inrush (See https://github.com/arendst/Tasmota/discussions/15695#discussioncomment-3747975)
Vo - Voltage
Po - Power
Ot - Overtemp
@@ -1495,7 +1481,7 @@ void SSPMHandleReceivedData(void) {
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]);
bool more = false;
char border[2][4] = { "Max","Min" };
- char stemp[10]; // "Tbd1|Voltage|Current|Power|Tbd2|Tbd3|Tbd4"
+ char stemp[10]; // "Tbd1|Voltage|Current|Power|Surge|Tbd6|Tbd7"
Response_P(PSTR("{\"SSPMOverload\":"));
for (uint32_t i = 0; i < 7; i++) {
uint32_t channel = SspmBuffer[32 +i];
@@ -1503,7 +1489,7 @@ void SSPMHandleReceivedData(void) {
if (channel &1) {
uint32_t relay = (module << 2) +(j & 3);
uint32_t idx = (j >> 2) & 1;
- ResponseAppend_P(PSTR("%s{\"L%d\":\"%s%s\"}"), (more)?",":"[", relay +1, border[idx], GetTextIndexed(stemp, sizeof(stemp), i, kSSPMOverload));
+ ResponseAppend_P(PSTR("%s{\"L%d\":\"%s%s\"}"), (more)?",":"[", relay +1, (4==i)?"":border[idx], GetTextIndexed(stemp, sizeof(stemp), i, kSSPMOverload));
more = true;
}
channel >>= 1;
@@ -1803,7 +1789,7 @@ void SSPMInit(void) {
return;
}
- SSPMSettingsLoad();
+ Xdrv86SettingsLoad();
pinMode(SSPM_GPIO_ARM_RESET, OUTPUT);
digitalWrite(SSPM_GPIO_ARM_RESET, 1);
@@ -2504,7 +2490,7 @@ bool Xdrv86(uint8_t function) {
SSPMEvery100ms();
break;
case FUNC_SAVE_SETTINGS:
- SSPMSettingsSave();
+ Xdrv86SettingsSave();
break;
case FUNC_SET_DEVICE_POWER:
result = SSPMSetDevicePower();
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino b/tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino
new file mode 100644
index 000000000..b0e9fe188
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino
@@ -0,0 +1,593 @@
+/*
+ xdrv_87_esp32_sonoff_tm1621.ino - Sonoff POWR3xxD and THR3xxD display support for Tasmota
+
+ SPDX-FileCopyrightText: 2022 Theo Arends
+
+ SPDX-License-Identifier: GPL-3.0-only
+*/
+
+#ifdef ESP32
+#ifdef USE_DISPLAY_TM1621_SONOFF
+/*********************************************************************************************\
+ * Sonoff POWR3xxD and THR3xxD LCD support
+ *
+ * {"NAME":"Sonoff POWR316D","GPIO":[32,0,0,0,0,576,0,0,0,224,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff POWR320D","GPIO":[32,0,9313,0,9312,576,0,0,0,0,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff THR316D","GPIO":[32,0,0,0,225,9280,0,0,0,321,0,576,320,9184,9216,0,0,224,0,9248,0,1,0,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff THR316D GPIO26","GPIO":[32,0,0,0,225,9280,0,0,0,321,0,576,320,9184,9216,0,0,224,0,9248,0,1,1,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff THR320D","GPIO":[32,0,0,0,226,9280,0,0,0,321,0,576,320,9184,9216,9312,0,0,9313,9248,0,1,0,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff THR320D GPIO26","GPIO":[32,0,0,0,226,9280,0,0,0,321,0,576,320,9184,9216,9312,0,0,9313,9248,0,1,1,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ *
+ * DspSpeed 2..127 = Display rotation speed in seconds if more than one value is requested
+ * DspLine<1|2> ,,,,... = Display specific JSON value and rotate between them
+ * unit 0 = None
+ * 1 = Temperature (Line 1 only)
+ * 2 = %RH (Line 2 only)
+ * 3 = Both V (Line 1 only) / A (Line 2 only)
+ * 4 = Both kWh (Line 1 only) / W (Line 2 only)
+ * DspLine1 0 and DspLine2 0 = Default of temperature/humidity
+ * DspLine9 = Show sensor data
+ *
+ * Example: {"SCD30":{"CarbonDioxide":746,"eCO2":727,"Temperature":30.6,"Humidity":43.6,"DewPoint":16.8}}
+ * index: 1 2 3 4 5 6
+ * unit: 0 0 (ppm) 0 (ppm) 1 (C or F) 2 (%RH) 1 (C or F)
+ *
+ * DspLine1 4,1,3,0 = Temperature and eCO2
+ * DspLine2 2,0,5,2 = CarbonDioxide and humidity
+\*********************************************************************************************/
+
+#define XDRV_87 87
+
+#define TM1621_ROTATE 5 // Seconds display rotation speed
+#define TM1621_MAX_VALUES 8 // Default 8 x two different lines
+
+#define TM1621_PULSE_WIDTH 10 // microseconds (Sonoff = 100)
+
+#define TM1621_SYS_EN 0x01 // 0b00000001
+#define TM1621_LCD_ON 0x03 // 0b00000011
+#define TM1621_TIMER_DIS 0x04 // 0b00000100
+#define TM1621_WDT_DIS 0x05 // 0b00000101
+#define TM1621_TONE_OFF 0x08 // 0b00001000
+#define TM1621_BIAS 0x29 // 0b00101001 = LCD 1/3 bias 4 commons option
+#define TM1621_IRQ_DIS 0x80 // 0b100x0xxx
+
+enum Tm1621Device { TM1621_USER, TM1621_POWR316D, TM1621_THR316D };
+enum Tm1621Units { TM1621_NONE, TM1621_TEMPERATURE, TM1621_HUMIDITY, TM1621_VOLTAGE_CURRENT, TM1621_ENERGY_POWER };
+
+const uint8_t tm1621_commands[] = { TM1621_SYS_EN, TM1621_LCD_ON, TM1621_BIAS, TM1621_TIMER_DIS, TM1621_WDT_DIS, TM1621_TONE_OFF, TM1621_IRQ_DIS };
+
+const char tm1621_kchar[] PROGMEM = { "0|1|2|3|4|5|6|7|8|9|-| " };
+// 0 1 2 3 4 5 6 7 8 9 - off
+const uint8_t tm1621_digit_row[2][12] = {{ 0x5F, 0x50, 0x3D, 0x79, 0x72, 0x6B, 0x6F, 0x51, 0x7F, 0x7B, 0x20, 0x00 },
+ { 0xF5, 0x05, 0xB6, 0x97, 0x47, 0xD3, 0xF3, 0x85, 0xF7, 0xD7, 0x02, 0x00 }};
+
+struct Tm1621 {
+ uint8_t buffer[8];
+ char row[2][12];
+ uint8_t pin_da;
+ uint8_t pin_cs;
+ uint8_t pin_rd;
+ uint8_t pin_wr;
+ uint8_t state;
+ uint8_t device;
+ uint8_t display_rotate;
+ uint8_t temp_sensors;
+ uint8_t temp_sensors_rotate;
+ bool celsius;
+ bool fahrenheit;
+ bool humidity;
+ bool voltage;
+ bool kwh;
+ bool present;
+} Tm1621;
+
+/*********************************************************************************************\
+ * Driver Settings load and save using filesystem
+\*********************************************************************************************/
+
+const uint32_t XDRV_87_VERSION = 0x0104; // Latest driver version (See settings deltas below)
+
+typedef struct {
+ uint32_t crc32; // To detect file changes
+ uint16_t version; // To detect driver function changes
+ uint8_t rotate;
+ uint8_t spare;
+ uint8_t line[2][TM1621_MAX_VALUES];
+ uint8_t unit[2][TM1621_MAX_VALUES];
+} tXdrv87Settings;
+tXdrv87Settings Xdrv87Settings;
+
+/*********************************************************************************************/
+
+void Xdrv87SettingsLoad(void) {
+ // *** Start init default values in case file is not found ***
+ memset(&Xdrv87Settings, 0x00, sizeof(tXdrv87Settings));
+ Xdrv87Settings.version = XDRV_87_VERSION;
+ // Init any other parameter in struct
+ Xdrv87Settings.rotate = TM1621_ROTATE;
+
+ // *** End Init default values ***
+
+#ifndef USE_UFILESYS
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 Use defaults as file system not enabled"));
+#else
+ // Try to load file /.drvset087
+ char filename[20];
+ // Use for drivers:
+ snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_87);
+ if (TfsLoadFile(filename, (uint8_t*)&Xdrv87Settings, sizeof(tXdrv87Settings))) {
+ if (Xdrv87Settings.version != XDRV_87_VERSION) { // Fix version dependent changes
+
+ // *** Start fix possible setting deltas ***
+// if (Xdrv87Settings.version < 0x0105) {
+// Xdrv87Settings.spare = test;
+// }
+
+ // *** End setting deltas ***
+
+ // Set current version and save settings
+ Xdrv87Settings.version = XDRV_87_VERSION;
+ Xdrv87SettingsSave();
+ }
+ AddLog(LOG_LEVEL_INFO, PSTR("CFG: XDRV87 loaded from file"));
+ } else {
+ // File system not ready: No flash space reserved for file system
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 Use defaults as file system not ready or file not found"));
+ }
+#endif // USE_UFILESYS
+}
+
+void Xdrv87SettingsSave(void) {
+#ifdef USE_UFILESYS
+ // Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
+ uint32_t crc32 = GetCfgCrc32((uint8_t*)&Xdrv87Settings +4, sizeof(tXdrv87Settings) -4); // Skip crc32
+ if (crc32 != Xdrv87Settings.crc32) {
+ // Try to save file /.drvset087
+ Xdrv87Settings.crc32 = crc32;
+
+ char filename[20];
+ // Use for drivers:
+ snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_87);
+ if (TfsSaveFile(filename, (const uint8_t*)&Xdrv87Settings, sizeof(tXdrv87Settings))) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 saved to file"));
+ } else {
+ // File system not ready: No flash space reserved for file system
+ AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 ERROR File system not ready or unable to save file"));
+ }
+ }
+#endif // USE_UFILESYS
+}
+
+/*********************************************************************************************/
+
+void TM1621StopSequence(void) {
+ digitalWrite(Tm1621.pin_cs, 1); // Stop command sequence
+ delayMicroseconds(TM1621_PULSE_WIDTH / 2);
+ digitalWrite(Tm1621.pin_da, 1); // Reset data
+}
+
+void TM1621SendCmnd(uint16_t command) {
+ uint16_t full_command = (0x0400 | command) << 5; // 0b100cccccccc00000
+ digitalWrite(Tm1621.pin_cs, 0); // Start command sequence
+ delayMicroseconds(TM1621_PULSE_WIDTH / 2);
+ for (uint32_t i = 0; i < 12; i++) {
+ digitalWrite(Tm1621.pin_wr, 0); // Start write sequence
+ if (full_command & 0x8000) {
+ digitalWrite(Tm1621.pin_da, 1); // Set data
+ } else {
+ digitalWrite(Tm1621.pin_da, 0); // Set data
+ }
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ digitalWrite(Tm1621.pin_wr, 1); // Read data
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ full_command <<= 1;
+ }
+ TM1621StopSequence();
+}
+
+void TM1621SendAddress(uint16_t address) {
+ uint16_t full_address = (address | 0x0140) << 7; // 0b101aaaaaa0000000
+ digitalWrite(Tm1621.pin_cs, 0); // Start command sequence
+ delayMicroseconds(TM1621_PULSE_WIDTH / 2);
+ for (uint32_t i = 0; i < 9; i++) {
+ digitalWrite(Tm1621.pin_wr, 0); // Start write sequence
+ if (full_address & 0x8000) {
+ digitalWrite(Tm1621.pin_da, 1); // Set data
+ } else {
+ digitalWrite(Tm1621.pin_da, 0); // Set data
+ }
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ digitalWrite(Tm1621.pin_wr, 1); // Read data
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ full_address <<= 1;
+ }
+}
+
+void TM1621SendCommon(uint8_t common) {
+ for (uint32_t i = 0; i < 8; i++) {
+ digitalWrite(Tm1621.pin_wr, 0); // Start write sequence
+ if (common & 1) {
+ digitalWrite(Tm1621.pin_da, 1); // Set data
+ } else {
+ digitalWrite(Tm1621.pin_da, 0); // Set data
+ }
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ digitalWrite(Tm1621.pin_wr, 1); // Read data
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ common >>= 1;
+ }
+}
+
+void TM1621SendRows(void) {
+ // Tm1621.row[x] = "text", "----", " " or a number with one decimal like "0.4", "237.5", "123456.7"
+ // "123456.7" will be shown as "9999" being a four digit overflow
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Row1 '%s', Row2 '%s'"), Tm1621.row[0], Tm1621.row[1]);
+
+ uint8_t buffer[8] = { 0 }; // TM1621 16-segment 4-bit common buffer
+ char row[4];
+ for (uint32_t j = 0; j < 2; j++) {
+ // 0.4V => " 04", 0.0A => " ", 1234.5V => "1234"
+ uint32_t len = strlen(Tm1621.row[j]);
+ char *dp = nullptr; // Expect number larger than "123"
+ int row_idx = len -3; // "1234.5"
+ if (len <= 5) { // "----", " ", "0.4", "237.5"
+ dp = strchr(Tm1621.row[j], '.');
+ row_idx = len -1;
+ }
+ else if (len > 6) { // "12345.6"
+ snprintf_P(Tm1621.row[j], sizeof(Tm1621.row[j]), PSTR("9999"));
+ row_idx = 3;
+ }
+ row[3] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' ';
+ if ((row_idx >= 0) && dp) { row_idx--; }
+ row[2] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' ';
+ row[1] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' ';
+ row[0] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' ';
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump%d %4_H"), j +1, row);
+
+ char command[10];
+ char needle[2] = { 0 };
+ for (uint32_t i = 0; i < 4; i++) {
+ needle[0] = row[i];
+ int index = GetCommandCode(command, sizeof(command), (const char*)needle, tm1621_kchar);
+ if (-1 == index) { index = 11; }
+ uint32_t bidx = (0 == j) ? i : 7 -i;
+ buffer[bidx] = tm1621_digit_row[j][index];
+ }
+ if (dp) {
+ if (0 == j) {
+ buffer[2] |= 0x80; // Row 1 decimal point
+ } else {
+ buffer[5] |= 0x08; // Row 2 decimal point
+ }
+ }
+ }
+
+ if (Tm1621.fahrenheit) { buffer[1] |= 0x80; }
+ if (Tm1621.celsius) { buffer[3] |= 0x80; }
+ if (Tm1621.kwh) { buffer[4] |= 0x08; }
+ if (Tm1621.humidity) { buffer[6] |= 0x08; }
+ if (Tm1621.voltage) { buffer[7] |= 0x08; }
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump3 %8_H"), buffer);
+
+ TM1621SendAddress(0x10); // Sonoff only uses the upper 16 Segments
+ for (uint32_t i = 0; i < 8; i++) {
+ TM1621SendCommon(buffer[i]);
+ }
+ TM1621StopSequence();
+}
+
+void TM1621PreInit(void) {
+ if (!PinUsed(GPIO_TM1621_CS) || !PinUsed(GPIO_TM1621_WR) || !PinUsed(GPIO_TM1621_RD) || !PinUsed(GPIO_TM1621_DAT)) { return; }
+
+ Tm1621.device = (14 == Pin(GPIO_TM1621_DAT)) ? TM1621_POWR316D : (5 == Pin(GPIO_TM1621_DAT)) ? TM1621_THR316D : TM1621_USER;
+ Tm1621.present = true;
+ Tm1621.pin_da = Pin(GPIO_TM1621_DAT);
+ Tm1621.pin_cs = Pin(GPIO_TM1621_CS);
+ Tm1621.pin_rd = Pin(GPIO_TM1621_RD);
+ Tm1621.pin_wr = Pin(GPIO_TM1621_WR);
+ pinMode(Tm1621.pin_da, OUTPUT);
+ digitalWrite(Tm1621.pin_da, 1);
+ pinMode(Tm1621.pin_cs, OUTPUT);
+ digitalWrite(Tm1621.pin_cs, 1);
+ pinMode(Tm1621.pin_rd, OUTPUT);
+ digitalWrite(Tm1621.pin_rd, 1);
+ pinMode(Tm1621.pin_wr, OUTPUT);
+ digitalWrite(Tm1621.pin_wr, 1);
+
+ Xdrv87SettingsLoad();
+
+ Tm1621.state = 200;
+
+ AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1621"));
+}
+
+void TM1621Init(void) {
+ digitalWrite(Tm1621.pin_cs, 0);
+ delayMicroseconds(80);
+ digitalWrite(Tm1621.pin_rd, 0);
+ delayMicroseconds(15);
+ digitalWrite(Tm1621.pin_wr, 0);
+ delayMicroseconds(25);
+ digitalWrite(Tm1621.pin_da, 0);
+ delayMicroseconds(TM1621_PULSE_WIDTH);
+ digitalWrite(Tm1621.pin_da, 1);
+
+ for (uint32_t command = 0; command < sizeof(tm1621_commands); command++) {
+ TM1621SendCmnd(tm1621_commands[command]);
+ }
+
+ TM1621SendAddress(0x00);
+ for (uint32_t segment = 0; segment < 16; segment++) {
+ TM1621SendCommon(0);
+ }
+ TM1621StopSequence();
+
+ snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("----"));
+ snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("----"));
+ TM1621SendRows();
+}
+
+uint32_t TM1621GetSensors(bool refresh) {
+ if (refresh) {
+ ResponseClear();
+ XsnsCall(FUNC_JSON_APPEND);
+ XdrvCall(FUNC_JSON_APPEND);
+ ResponseJsonStart(); // Overwrite first comma
+ ResponseJsonEnd(); // Append }
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TM1: Sensors %s"), ResponseData());
+ }
+ return ResponseLength();
+}
+
+float TM1621GetValues(uint32_t index, bool refresh) {
+ float value = NAN;
+ if (TM1621GetSensors(refresh)) {
+ uint32_t idx = 0;
+ char *data = ResponseData(); // {"HTU21":{"Temperature":30.7,"Humidity":39.0,"DewPoint":15.2},"BME680":{"Temperature":30.0,"Humidity":50.4,"DewPoint":18.5,"Pressure":1009.6,"Gas":1660.52},"ESP32":{"Temperature":53.3}}
+ while (data) {
+ data = strstr_P(data, PSTR("\":"));
+ if (data) {
+ idx++;
+ data += 2;
+ if (idx == index) {
+ value = CharToFloat(data);
+ break;
+ }
+ }
+ }
+ }
+ return value;
+}
+
+float TM1621GetTemperatureValues(uint32_t index) {
+ float value = NAN;
+ if (TM1621GetSensors(1)) {
+ uint32_t idx = 0;
+ char *data = ResponseData(); // {"HTU21":{"Temperature":30.7,"Humidity":39.0,"DewPoint":15.2},"BME680":{"Temperature":30.0,"Humidity":50.4,"DewPoint":18.5,"Pressure":1009.6,"Gas":1660.52},"ESP32":{"Temperature":53.3}}
+ while (data) {
+ data = strstr_P(data, PSTR(D_JSON_TEMPERATURE));
+ if (data) {
+ idx++;
+ data += 13; // strlen("Temperature") + 2;
+ if (idx == index) {
+ value = CharToFloat(data);
+ if (Tm1621.temp_sensors) {
+ break;
+ }
+ }
+ }
+ }
+ if (0 == Tm1621.temp_sensors) {
+ Tm1621.temp_sensors = idx;
+ }
+ }
+ return value;
+}
+
+void TM1621Show(void) {
+ Tm1621.celsius = false;
+ Tm1621.fahrenheit = false;
+ Tm1621.humidity = false;
+ Tm1621.voltage = false;
+ Tm1621.kwh = false;
+ snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR(" "));
+ snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR(" "));
+
+ if ((Xdrv87Settings.line[0][0] > 0) || (Xdrv87Settings.line[1][0] > 0)) {
+ float value = TM1621GetValues(Xdrv87Settings.line[0][Tm1621.display_rotate], 1);
+ if (!isnan(value)) {
+ ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &value);
+ if (TM1621_TEMPERATURE == Xdrv87Settings.unit[0][Tm1621.display_rotate]) {
+ if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
+ Tm1621.fahrenheit = true;
+ } else {
+ Tm1621.celsius = true;
+ }
+ }
+ Tm1621.voltage = (TM1621_VOLTAGE_CURRENT == Xdrv87Settings.unit[0][Tm1621.display_rotate]);
+ Tm1621.kwh = (4 == Xdrv87Settings.unit[0][Tm1621.display_rotate]);
+ }
+ value = TM1621GetValues(Xdrv87Settings.line[1][Tm1621.display_rotate], 0);
+ if (!isnan(value)) {
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &value);
+ Tm1621.humidity = (TM1621_HUMIDITY == Xdrv87Settings.unit[1][Tm1621.display_rotate]);
+ Tm1621.voltage = (TM1621_VOLTAGE_CURRENT == Xdrv87Settings.unit[1][Tm1621.display_rotate]);
+ Tm1621.kwh = (TM1621_ENERGY_POWER == Xdrv87Settings.unit[1][Tm1621.display_rotate]);
+ }
+ uint32_t max = 0;
+ while ((max < TM1621_MAX_VALUES) && ((Xdrv87Settings.line[0][max] > 0) || (Xdrv87Settings.line[1][max] > 0))) { max++; }
+ Tm1621.display_rotate++;
+ if (Tm1621.display_rotate >= max) {
+ Tm1621.display_rotate = 0;
+ }
+ TM1621SendRows();
+ return;
+ }
+
+ if (TM1621_POWR316D == Tm1621.device) {
+ if (0 == Tm1621.display_rotate) {
+ ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.voltage[0]);
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.current[0]);
+ Tm1621.voltage = true;
+ Tm1621.display_rotate = 1;
+ } else {
+ ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.total[0]);
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.active_power[0]);
+ Tm1621.kwh = true;
+ Tm1621.display_rotate = 0;
+ }
+ TM1621SendRows();
+ return;
+ }
+
+ if (TM1621_THR316D == Tm1621.device) {
+ if (!isnan(TasmotaGlobal.temperature_celsius)) {
+ float temperature = ConvertTempToFahrenheit(TasmotaGlobal.temperature_celsius);
+ if (TasmotaGlobal.humidity == 0.0f) { // No humidity so check for more temperature sensors
+ if (0 == Tm1621.temp_sensors) {
+ TM1621GetTemperatureValues(100); // Find max number of temperature sensors
+ }
+ if (Tm1621.temp_sensors > 1) {
+ if (Tm1621.temp_sensors > 2) {
+ Tm1621.temp_sensors_rotate++;
+ if (Tm1621.temp_sensors_rotate > Tm1621.temp_sensors) {
+ Tm1621.temp_sensors_rotate = 1;
+ }
+ temperature = TM1621GetTemperatureValues(Tm1621.temp_sensors_rotate);
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%d"), Tm1621.temp_sensors_rotate);
+ } else {
+ float temperature2 = TM1621GetTemperatureValues(2);
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &temperature2);
+ }
+ }
+ }
+ ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &temperature);
+ if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
+ Tm1621.fahrenheit = true;
+ } else {
+ Tm1621.celsius = true;
+ }
+ }
+ if (TasmotaGlobal.humidity > 0.0f) {
+ Tm1621.humidity = true;
+ ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &TasmotaGlobal.humidity);
+ }
+ }
+ TM1621SendRows();
+}
+
+void TM1621EverySecond(void) {
+ Tm1621.state++;
+ if (Tm1621.state > 127) {
+ if (202 == Tm1621.state) {
+ TM1621Init();
+ Tm1621.state = 0;
+ }
+ } else {
+ if (Tm1621.state >= Xdrv87Settings.rotate) {
+ TM1621Show();
+ Tm1621.state = 0;
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Command
+\*********************************************************************************************/
+
+const char kTm1621Commands[] PROGMEM = "Dsp|" // No prefix
+ "Line|Speed";
+void (*const kTm1621Command[])(void) PROGMEM = {
+ &CmndDspLine, &CmndDspSpeed };
+
+void CmndDspLine(void) {
+ // DspLine1 , | |
---|
|