From 0f1e4fc9174b86505a651ade178c97814833079c Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 23 Aug 2020 18:29:16 +0200 Subject: [PATCH 1/6] Add command ``PowerDelta1`` to ``PowerDelta3`` - Bump version to 8.4.0.3 - Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134) --- RELEASENOTES.md | 4 +- tasmota/CHANGELOG.md | 4 ++ tasmota/settings.h | 12 ++-- tasmota/settings.ino | 11 +++- tasmota/support_command.ino | 4 +- tasmota/tasmota_version.h | 2 +- tasmota/xdrv_03_energy.ino | 121 +++++++++++++++++++++--------------- 7 files changed, 97 insertions(+), 61 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 70c10750d..7bb3ee324 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -53,7 +53,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 8.4.0.2 +### Version 8.4.0.3 - Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1 - Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable`` @@ -63,6 +63,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046) - Add ESP32 Analog input support for GPIO32 to GPIO39 - Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig`` +- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors - Add better config corruption recovery (#9046) - Add virtual CT for 4 channels lights, emulating a 5th channel - Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113) +- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index a94fa3d3c..244e6e7b6 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased (development) +### 8.4.0.3 20200823 + +- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134) + ### 8.4.0.2 20200813 - Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1 diff --git a/tasmota/settings.h b/tasmota/settings.h index 0c40a0613..590d0af3e 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -127,7 +127,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t white_blend_mode : 1; // bit 23 (v8.4.0.1) - SetOption105 - White Blend Mode - used to be `RGBWWTable` last value `0`, now deprecated in favor of this option uint32_t virtual_ct : 1; // bit 24 (v8.4.0.1) - SetOption106 - Virtual CT - Creates a virtual White ColorTemp for RGBW lights uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false) - uint32_t teleinfo_rawdata : 1; // bit 21 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1) + uint32_t teleinfo_rawdata : 1; // bit 21 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1) uint32_t spare27 : 1; uint32_t spare28 : 1; // bit 28 uint32_t spare29 : 1; // bit 29 @@ -557,7 +557,7 @@ struct { uint16_t dimmer_hw_min; // E90 uint16_t dimmer_hw_max; // E92 uint32_t deepsleep; // E94 - uint16_t energy_power_delta; // E98 + uint16_t ex2_energy_power_delta; // E98 - Free since 8.4.0.3 uint8_t shutter_motordelay[MAX_SHUTTERS]; // E9A int8_t temp_comp; // E9E uint8_t weight_change; // E9F @@ -610,10 +610,14 @@ struct { uint8_t tcp_baudrate; // F41 uint8_t fallback_module; // F42 - uint8_t free_f43[113]; // F43 - Decrement if adding new Setting variables just above and below + uint8_t free_f43[1]; // F43 + + uint16_t energy_power_delta[3]; // F44 + + uint8_t free_f4e[106]; // F4A - Decrement if adding new Setting variables just above and below // Only 32 bit boundary variables below - SysBitfield5 flag5; // EB4 + SysBitfield5 flag5; // FB4 uint16_t pulse_counter_debounce_low; // FB8 uint16_t pulse_counter_debounce_high; // FBA uint32_t keeloq_master_msb; // FBC diff --git a/tasmota/settings.ino b/tasmota/settings.ino index c6d8c52f4..32cc85335 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -935,7 +935,9 @@ void SettingsDefaultSet2(void) flag3.dds2382_model |= ENERGY_DDS2382_MODE; flag3.hardware_energy_total |= ENERGY_HARDWARE_TOTALS; Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; -// Settings.energy_power_delta = 0; +// Settings.energy_power_delta[0] = 0; +// Settings.energy_power_delta[1] = 0; +// Settings.energy_power_delta[2] = 0; Settings.energy_power_calibration = HLW_PREF_PULSE; Settings.energy_voltage_calibration = HLW_UREF_PULSE; Settings.energy_current_calibration = HLW_IREF_PULSE; @@ -1346,7 +1348,7 @@ void SettingsDelta(void) Settings.ex_sbaudrate = 0; */ Settings.flag3.fast_power_cycle_disable = 0; - Settings.energy_power_delta = Settings.ex_energy_power_delta; + Settings.ex2_energy_power_delta = Settings.ex_energy_power_delta; Settings.ex_energy_power_delta = 0; } if (Settings.version < 0x06060015) { @@ -1503,6 +1505,11 @@ void SettingsDelta(void) if (Settings.version < 0x08030106) { Settings.fallback_module = FALLBACK_MODULE; } + if (Settings.version < 0x08040003) { + Settings.energy_power_delta[0] = Settings.ex2_energy_power_delta; + Settings.energy_power_delta[1] = 0; + Settings.energy_power_delta[2] = 0; + } Settings.version = VERSION; SettingsSave(1); diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 703ee0a67..379d15e0c 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -538,9 +538,9 @@ void CmndStatus(void) #if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION) if (energy_flg) { if ((0 == payload) || (9 == payload)) { - Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":%d,\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":[%d,%d,%d],\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), - Settings.energy_power_delta, Settings.energy_min_power, Settings.energy_max_power, + Settings.energy_power_delta[0], Settings.energy_power_delta[1], Settings.energy_power_delta[2], Settings.energy_min_power, Settings.energy_max_power, Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9")); } diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h index 4d0d9e0f2..ce7e073c2 100644 --- a/tasmota/tasmota_version.h +++ b/tasmota/tasmota_version.h @@ -20,7 +20,7 @@ #ifndef _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_ -const uint32_t VERSION = 0x08040002; +const uint32_t VERSION = 0x08040003; // Lowest compatible version const uint32_t VERSION_COMPATIBLE = 0x07010006; diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index 4d53af77c..8c33c6ee3 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -108,7 +108,7 @@ struct ENERGY { bool power_on = true; #ifdef USE_ENERGY_MARGIN_DETECTION - uint16_t power_history[3] = { 0 }; + uint16_t power_history[3][3] = {{ 0 }, { 0 }, { 0 }}; uint8_t power_steady_counter = 8; // Allow for power on stabilization bool min_power_flag = false; bool max_power_flag = false; @@ -130,6 +130,31 @@ Ticker ticker_energy; /********************************************************************************************/ +char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false) +{ + char layout[16]; + GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases); + switch (index) { + case 2: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); // Dirty + break; + case 3: + snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); // Even dirtier + break; + default: + snprintf_P(result, FLOATSZ *3, input); + } + return result; +} + +char* EnergyFormat(char* result, char* input, bool json, bool single = false) +{ + uint8_t index = (single) ? 1 : Energy.phase_count; // 1,2,3 + return EnergyFormatIndex(result, input, json, index, single); +} + +/********************************************************************************************/ + bool EnergyTariff1Active() // Off-Peak hours { uint8_t dst = 0; @@ -303,38 +328,53 @@ void EnergyMarginCheck(void) return; } - uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]); - bool jsonflg = false; Response_P(PSTR("{\"" D_RSLT_MARGINS "\":{")); - if (Settings.energy_power_delta) { - int16_t power_diff = energy_power_u - Energy.power_history[0]; - uint16_t delta = abs(power_diff); - if (delta > 0) { - if (Settings.energy_power_delta < 101) { // 1..100 = Percentage - uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0]; - if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741) - delta = (delta * 100) / min_power; - if (delta > Settings.energy_power_delta) { - jsonflg = true; - } - } else { // 101..32000 = Absolute - if (delta > (Settings.energy_power_delta -100)) { - jsonflg = true; + int16_t power_diff[3] = { 0 }; + for (uint32_t phase = 0; phase < Energy.phase_count; phase++) { + uint16_t active_power = (uint16_t)(Energy.active_power[phase]); + + if (Settings.energy_power_delta[phase]) { + power_diff[phase] = active_power - Energy.power_history[phase][0]; + uint16_t delta = abs(power_diff[phase]); + bool threshold_met = false; + if (delta > 0) { + if (Settings.energy_power_delta[phase] < 101) { // 1..100 = Percentage + uint16_t min_power = (Energy.power_history[phase][0] > active_power) ? active_power : Energy.power_history[phase][0]; + if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741) + delta = (delta * 100) / min_power; + if (delta > Settings.energy_power_delta[phase]) { + threshold_met = true; + } + } else { // 101..32000 = Absolute + if (delta > (Settings.energy_power_delta[phase] -100)) { + threshold_met = true; + } } } - if (jsonflg) { - Energy.power_history[1] = Energy.active_power[0]; // We only want one report so reset history - Energy.power_history[2] = Energy.active_power[0]; - - ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%d"), power_diff); + if (threshold_met) { + Energy.power_history[phase][1] = active_power; // We only want one report so reset history + Energy.power_history[phase][2] = active_power; + jsonflg = true; + } else { + power_diff[phase] = 0; } } + Energy.power_history[phase][0] = Energy.power_history[phase][1]; // Shift in history every second allowing power changes to settle for up to three seconds + Energy.power_history[phase][1] = Energy.power_history[phase][2]; + Energy.power_history[phase][2] = active_power; } - Energy.power_history[0] = Energy.power_history[1]; // Shift in history every second allowing power changes to settle for up to three seconds - Energy.power_history[1] = Energy.power_history[2]; - Energy.power_history[2] = energy_power_u; + if (jsonflg) { + char power_diff_chr[Energy.phase_count][FLOATSZ]; + for (uint32_t phase = 0; phase < Energy.phase_count; phase++) { + dtostrfd(power_diff[phase], 0, power_diff_chr[phase]); + } + char value_chr[FLOATSZ *3]; + ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%s"), EnergyFormat(value_chr, power_diff_chr[0], 1)); + } + + uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]); if (Energy.power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) { uint16_t energy_voltage_u = (uint16_t)(Energy.voltage[0]); @@ -725,10 +765,12 @@ void CmndModuleAddress(void) #ifdef USE_ENERGY_MARGIN_DETECTION void CmndPowerDelta(void) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) { - Settings.energy_power_delta = XdrvMailbox.payload; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) { + Settings.energy_power_delta[XdrvMailbox.index -1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.energy_power_delta[XdrvMailbox.index -1]); } - ResponseCmndNumber(Settings.energy_power_delta); } void CmndPowerLow(void) @@ -881,29 +923,6 @@ const char HTTP_ENERGY_SNS3[] PROGMEM = "{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; #endif // USE_WEBSERVER -char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false) -{ - char layout[16]; - GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases); - switch (index) { - case 2: - snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); // Dirty - break; - case 3: - snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); // Even dirtier - break; - default: - snprintf_P(result, FLOATSZ *3, input); - } - return result; -} - -char* EnergyFormat(char* result, char* input, bool json, bool single = false) -{ - uint8_t index = (single) ? 1 : Energy.phase_count; // 1,2,3 - return EnergyFormatIndex(result, input, json, index, single); -} - void EnergyShow(bool json) { for (uint32_t i = 0; i < Energy.phase_count; i++) { From ea0901b162705dbd3d207f1464fdfb4c3bde6ea9 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Mon, 24 Aug 2020 21:07:03 +0200 Subject: [PATCH 2/6] Add Zigbee battery icon --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_01_webserver.ino | 35 +++++++++++++++++++++++++++++++ tasmota/xdrv_23_zigbee_A_impl.ino | 6 +++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 244e6e7b6..7118053e0 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -13,6 +13,7 @@ - Add virtual CT for 4 channels lights, emulating a 5th channel - Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113) - Add Zigbee web gui widget for Temp/Humidity/Pressure sensors +- Add Zigbee battery icon ### 8.4.0.1 20200730 diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 11e0281b4..96e1e5a54 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -620,6 +620,38 @@ const char HTTP_HEAD_STYLE2[] PROGMEM = ".r{border-radius:0.3em;padding:2px;margin:6px 2px;}"; #endif //USE_UNISHOX_COMPRESSION +#ifdef USE_ZIGBEE +// Styles used for Zigbee Web UI +// Battery icon from https://css.gg/battery +// +#ifdef USE_UNISHOX_COMPRESSION + +const size_t HTTP_HEAD_STYLE_ZIGBEE_SIZE = 363; +const char HTTP_HEAD_STYLE_ZIGBEE_COMPRESSED[] PROGMEM = "\x3A\x0E\xA3\xDA\x3B\x0D\x87\x5F\xB4\xDB\xBC\x3C\x79\x8E\xCF\x88\xFE\x75\x8E\xC3" + "\x61\xE0\x66\x7B\x6B\x73\x8F\x3F\xB0\xAE\xB4\xCD\x9E\x04\xDF\x0C\x0A\xCC\x8F\x3D" + "\xE0\xB7\x99\xD6\x38\x2C\x0C\xD0\xF0\x3F\xA2\x50\xA3\xCC\xE5\x32\x18\x6C\x3C\x0A" + "\x7A\x3C\x2A\x2B\x8F\x33\x92\x88\x61\xB0\xF0\x08\x39\x29\xE6\x72\x88\x61\xB1\x7B" + "\x02\xD1\x01\x0A\x69\xD7\xFB\x13\x45\xF8\xF3\x39\x64\x30\xD8\x78\x1B\x7F\x1E\xDE" + "\x3A\xC2\x66\x28\xF3\x3A\xCE\x59\x0C\x36\x1E\xE3\xA0\xEA\x3C\xCF\x3B\x31\x4F\xE7" + "\x51\xD0\x75\x1E\x67\x98\xE6\x63\x3E\xCF\x68\x79\xD4\xFA\x8F\x33\xD8\x7B\x01\x13" + "\x5E\x04\x1D\x5C\x16\xB8\x14\xB1\xDE\xC0\x85\xD3\x04\x3D\xD0\xE7\x10\xC3\x61\xE0" + "\x75\x86\x68\x3D\xFC\x17\xC2\x1E\x61\x8B\xFF\xDF\x51\x07\x81\x67\xCF\x15\x83\x0F" + "\x33\x90\x81\x0F\x5F\x04\x2D\x53\xFA\x3C\x2A\x2B\x8F\x33\xAC\xE6\x10\x22\x70\x54" + "\x08\xFC\x0C\x82\x0F\x0A\x67\x30\x81\x23\x81\x23\xDA\x08\x34\x4C\xEF\xE7\x74\xEB" + "\x3A\xC7\x04\x75\x1C\x98\x43\x0D\x87\x78\xF0\x13\x31\x47\x99\xC8\x43\x0D\x87\xB8"; + +// Raw: .bt{box-sizing:border-box;position:relative;display:inline-block;width:20px;height:12px;border:2px solid;border-radius:3px;margin-left:-3px}.bt::after,.bt::before{content:"";display:block;box-sizing:border-box;position:absolute;height:6px;background:currentColor;top:1px}.bt::before{right:-4px;border-radius:3px;width:4px}.bt::after{width:var(--bl,14px);left:1px} +// Successfully compressed from 363 to 240 bytes (-33.9%) +#define HTTP_HEAD_STYLE_ZIGBEE Decompress(HTTP_HEAD_STYLE_ZIGBEE_COMPRESSED,HTTP_HEAD_STYLE_ZIGBEE_SIZE).c_str() +#else // USE_UNISHOX_COMPRESSION +const char HTTP_HEAD_STYLE_ZIGBEE[] PROGMEM = + ".bt{box-sizing:border-box;position:relative;display:inline-block;width:20px;height:12px;border:2px solid;border-radius:3px;margin-left:-3px}" + ".bt::after,.bt::before{content:\"\";display:block;box-sizing:border-box;position:absolute;height:6px;background:currentColor;top:1px}" + ".bt::before{right:-4px;border-radius:3px;width:4px}" + ".bt::after{width:var(--bl,14px);left:1px}"; +#endif // USE_UNISHOX_COMPRESSION +#endif // USE_ZIGBEE + const char HTTP_HEAD_STYLE3[] PROGMEM = "" @@ -1113,6 +1145,9 @@ void WSContentSendStyle_P(const char* formatP, ...) WSContentSend_P(HTTP_HEAD_STYLE2, WebColor(COL_BUTTON), WebColor(COL_BUTTON_TEXT), WebColor(COL_BUTTON_HOVER), WebColor(COL_BUTTON_RESET), WebColor(COL_BUTTON_RESET_HOVER), WebColor(COL_BUTTON_SAVE), WebColor(COL_BUTTON_SAVE_HOVER), WebColor(COL_BUTTON)); +#ifdef USE_ZIGBEE + WSContentSend_P(HTTP_HEAD_STYLE_ZIGBEE); +#endif // USE_ZIGBEE if (formatP != nullptr) { // This uses char strings. Be aware of sending %% if % is needed va_list arg; diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 088196a11..73cd9e4a6 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1344,7 +1344,7 @@ void ZigbeeShow(bool json) if (zigbee_num > 255) { zigbee_num = 255; } // Calculate fixed column width for best visual result (Theos opinion) - const uint8_t px_batt = (strlen(D_BATT) + 5 + 1) * 10; // Batt 100% = 90px + 10px column separator + const uint8_t px_batt = 30; // Battery icon is 20px, add 10px as separator const uint8_t px_lqi = (strlen(D_LQI) + 4) * 10; // LQI 254 = 70px WSContentSend_P(PSTR("{t}")); // Terminate current two column table and open new table @@ -1373,10 +1373,10 @@ void ZigbeeShow(bool json) snprintf_P(slqi, sizeof(slqi), PSTR("%d"), device.lqi); } - char sbatt[20]; + char sbatt[64]; snprintf_P(sbatt, sizeof(sbatt), PSTR(" ")); if (device.validBatteryPercent()) { - snprintf_P(sbatt, sizeof(sbatt), PSTR(D_BATT " %d%%"), device.batterypercent); + snprintf_P(sbatt, sizeof(sbatt), PSTR(""), device.batterypercent, changeUIntScale(device.batterypercent, 0, 100, 0, 14)); } if (!i) { // First row needs style info From e5a02772f0db5e43fa821b41aa8527f911497a08 Mon Sep 17 00:00:00 2001 From: "Povl H. Pedersen" Date: Tue, 25 Aug 2020 09:33:27 +0200 Subject: [PATCH 3/6] Update xnrg_01_hlw8012.ino Fix rounding error in kWhtoday_delta calculation. At 3.6kW load, hlw_len could be as low as 5, so rounding could make the value 20% off. Multiply hlw_len by 1000 and the dividend a few lines below as well fixes this. With integer math only, dividends should keep precision as long as possible. --- tasmota/xnrg_01_hlw8012.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasmota/xnrg_01_hlw8012.ino b/tasmota/xnrg_01_hlw8012.ino index 3a0873dd1..ae07a6102 100644 --- a/tasmota/xnrg_01_hlw8012.ino +++ b/tasmota/xnrg_01_hlw8012.ino @@ -205,10 +205,10 @@ void HlwEverySecond(void) unsigned long hlw_len; if (Hlw.energy_period_counter) { - hlw_len = 10000 / Hlw.energy_period_counter; + hlw_len = 10000 * 1000 / Hlw.energy_period_counter; Hlw.energy_period_counter = 0; if (hlw_len) { - Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) / hlw_len) / 36; + Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) * 1000 / hlw_len) / 36; EnergyUpdateToday(); } } From aa4d8d3d24b98a774292a8bfe98b1bf1ba0a840b Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Tue, 25 Aug 2020 19:01:24 +0200 Subject: [PATCH 4/6] Zigbee adding decoder for Aqara door sensor battery --- tasmota/xdrv_23_zigbee_5_converters.ino | 57 +++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index bde944132..b20aaa2d0 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -136,9 +136,10 @@ enum Z_ConvOperators { Z_ManufKeep, // copy and record Manufacturer attribute Z_ModelKeep, // copy and record ModelId attribute Z_AqaraSensor, // decode prioprietary Aqara Sensor message + Z_AqaraSensor2, // decode prioprietary Aqara Sensor message V2 Z_AqaraVibration, // decode Aqara vibration modes Z_AqaraCube, // decode Aqara cube - Z_AqaraButton, // decode Aqara button + Z_AqaraButton, // decode Aqara button Z_BatteryPercentage, // memorize Battery Percentage in RAM }; @@ -159,7 +160,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Zstring, Cx0000, 0x4000, Z_(SWBuildID), 1, Z_Nop }, // { Zunk, Cx0000, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary - { Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor }, // Occupancy (map8) + { Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor }, + { Zmap8, Cx0000, 0xFF02, Z_(), 0, Z_AqaraSensor2 }, // Power Configuration cluster { Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), 1, Z_Nop }, @@ -909,6 +911,24 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer // i += buf.get8(i) + 1; break; + case Zstruct: + { + uint16_t struct_size = buf.get16(i); + len = 2; + if (0xFFFF != struct_size) { + if (struct_size > 16) { struct_size = 16; } + // parse inner attributes - supports only fixed length for now + for (uint32_t j = 0; j < struct_size; j++) { + uint8_t attr_type = buf.get8(i+len); + len += Z_getDatatypeLen(attr_type) + 1; + } + char hex[2*len+1]; + ToHex_P(buf.buf(i), len, hex, sizeof(hex)); + json[attrid_str] = hex; + } + } + break; + case Zdata8: // data8 case Zmap8: // map8 { @@ -1378,7 +1398,6 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj uint32_t len = buf2.len(); char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused - JsonVariant sub_value; const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown String modelId((char*) modelId_c); @@ -1435,6 +1454,35 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj } return 1; // remove original key } + +int32_t Z_AqaraSensorFunc2(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { + String hex = value; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint32_t i = 0; + uint32_t len = buf2.len(); + + // Look for battery value which is the first attribute of type 0x21 + uint16_t struct_size = buf2.get16(0); + size_t struct_len = 2; + if (0xFFFF != struct_size) { + if (struct_size > 16) { struct_size = 16; } + for (uint32_t j = 0; (j < struct_size) && (struct_len < len); j++) { + uint8_t attr_type = buf2.get8(struct_len); + if (0x21 == attr_type) { + uint16_t val = buf2.get16(struct_len+1); + float batteryvoltage = val / 1000.0f; + json[F("BatteryVoltage")] = batteryvoltage; + uint8_t batterypercentage = toPercentageCR2032(val); + json[F("BatteryPercentage")] = batterypercentage; + zigbee_devices.setBatteryPercent(shortaddr, batterypercentage); + break; + } + struct_len += Z_getDatatypeLen(attr_type) + 1; + } + } + + return 0; // remove original key +} // ====================================================================== // apply the transformation from the converter @@ -1468,6 +1516,9 @@ int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje case Z_AqaraSensor: func = &Z_AqaraSensorFunc; break; + case Z_AqaraSensor2: + func = &Z_AqaraSensorFunc2; + break; case Z_AqaraVibration: func = &Z_AqaraVibrationFunc; break; From d8eae9209161937890bd1978e2aa9f52ac154f2f Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Tue, 25 Aug 2020 19:54:19 +0200 Subject: [PATCH 5/6] Zigbee support Philips presence sensor --- tasmota/xdrv_23_zigbee_5_converters.ino | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index bde944132..77f23736e 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1018,6 +1018,16 @@ void ZCLFrame::parseReportAttributes(JsonObject& json, uint8_t offset) { } i += parseSingleAttribute(json, key, _payload, i); } + + // Issue Philips outdoor motion sensor SML002, see https://github.com/Koenkk/zigbee2mqtt/issues/897 + // The sensor expects the coordinator to send a Default Response to acknowledge the attribute reporting + if (0 == _frame_control.b.disable_def_resp) { + // the device expects a default response + SBuffer buf(2); + buf.add8(_cmd_id); + buf.add8(0x00); // Status = OK + ZigbeeZCLSend_Raw(_srcaddr, 0x0000, 0x0000 /*cluster*/, _srcendpoint, ZCL_DEFAULT_RESPONSE, false /* not cluster specific */, _manuf_code, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(_srcaddr)); + } } // ZCL_READ_ATTRIBUTES From d8f726a0c96811e4552ce5596880e71114ad63d6 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Wed, 26 Aug 2020 08:56:13 +0200 Subject: [PATCH 6/6] Add Zigbee web ui widget for Lights --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_23_zigbee_2_devices.ino | 38 ++++++++++++++--------- tasmota/xdrv_23_zigbee_7_statemachine.ino | 2 +- tasmota/xdrv_23_zigbee_A_impl.ino | 34 +++++++++++++++++--- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 7118053e0..96ae2be02 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.4.0.3 20200823 - Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134) +- Add Zigbee web ui widget for Lights ### 8.4.0.2 20200813 diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index a7bd2adee..68ba55d42 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -146,6 +146,14 @@ public: inline bool getReachable(void) const { return bitRead(power, 7); } inline void setPower(bool power_on) { bitWrite(power, 0, power_on); bitWrite(power, 1, false); } inline bool getPower(void) const { return bitRead(power, 0); } + + // If light, returns the number of channels, or 0xFF if unknown + uint8_t getLightChannels(void) const { + if ((zb_profile & 0xF0) == 0x00) { + return zb_profile & 0x07; + } + return 0xFF; + } }; /*********************************************************************************************\ @@ -682,21 +690,21 @@ void Z_Devices::updateZbProfile(uint16_t shortaddr) { { uint32_t channels = zb_profile & 0x07; // depending on the bulb type, the default parameters from unknown to credible defaults - if (!device.validPower()) { device.setPower(false); } - if (1 <= channels) { - if (0xFF == device.dimmer) { device.dimmer = 0; } - } - if (3 <= channels) { - if (0xFF == device.sat) { device.sat = 0; } - if (0xFFFF == device.hue) { device.hue = 0; } - if (0xFFFF == device.x) { device.x = 0; } - if (0xFFFF == device.y) { device.y = 0; } - if (0xFF == device.colormode) { device.colormode = 0; } // HueSat mode - } - if ((2 == channels) || (5 == channels)) { - if (0xFFFF == device.ct) { device.ct = 200; } - if (0xFF == device.colormode) { device.colormode = 2; } // CT mode - } + // if (!device.validPower()) { device.setPower(false); } + // if (1 <= channels) { + // if (0xFF == device.dimmer) { device.dimmer = 0; } + // } + // if (3 <= channels) { + // if (0xFF == device.sat) { device.sat = 0; } + // if (0xFFFF == device.hue) { device.hue = 0; } + // if (0xFFFF == device.x) { device.x = 0; } + // if (0xFFFF == device.y) { device.y = 0; } + // if (0xFF == device.colormode) { device.colormode = 0; } // HueSat mode + // } + // if ((2 == channels) || (5 == channels)) { + // if (0xFFFF == device.ct) { device.ct = 200; } + // if (0xFF == device.colormode) { device.colormode = 2; } // CT mode + // } } break; } diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index ee9bfd19a..5a6ab4e29 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -681,7 +681,7 @@ ZBM(ZBS_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*false*/, 0 ZBM(ZBR_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*ok*/) // 100000 // setInitialSecurityState -#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY +#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY | EMBER_NO_FRAME_COUNTER_RESET ZBR(ZBS_SET_SECURITY, EZSP_setInitialSecurityState, 0x00 /*high*/, Z_B0(EZ_SECURITY_MODE), Z_B1(EZ_SECURITY_MODE), // preConfiguredKey diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 73cd9e4a6..1552eb166 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1348,6 +1348,7 @@ void ZigbeeShow(bool json) const uint8_t px_lqi = (strlen(D_LQI) + 4) * 10; // LQI 254 = 70px WSContentSend_P(PSTR("{t}")); // Terminate current two column table and open new table + WSContentSend_P(PSTR("")); // sort elements by name, then by id uint8_t sorted_idx[zigbee_num]; @@ -1393,20 +1394,45 @@ void ZigbeeShow(bool json) bool pressure_ok = device.validPressure(); if (temperature_ok || humidity_ok || pressure_ok) { - WSContentSend_P(PSTR("| ")); + WSContentSend_P(PSTR("┆")); if (temperature_ok) { char buf[12]; dtostrf(device.temperature / 10.0f, 3, 1, buf); - WSContentSend_PD(PSTR("  ☀️%s°C"), buf); + WSContentSend_PD(PSTR(" ☀️%s°C"), buf); } if (humidity_ok) { - WSContentSend_P(PSTR("  💧%d%%"), device.humidity); + WSContentSend_P(PSTR(" 💧%d%%"), device.humidity); } if (pressure_ok) { - WSContentSend_P(PSTR("  ⛅ %d hPa"), device.pressure); + WSContentSend_P(PSTR(" ⛅ %d hPa"), device.pressure); } WSContentSend_P(PSTR("{e}")); } + + // Light and switches + bool power_ok = device.validPower(); + if (power_ok) { + uint8_t channels = device.getLightChannels(); + if (0xFF == channels) { channels = 5; } // if number of channel is unknown, display all known attributes + WSContentSend_P(PSTR("┆ %s"), device.getPower() ? PSTR(D_ON) : PSTR(D_OFF)); + if (device.validDimmer() && (channels >= 1)) { + WSContentSend_P(PSTR(" 🔅%d%%"), changeUIntScale(device.dimmer,0,254,0,100)); + } + if (device.validCT() && ((channels == 2) || (channels == 5))) { + uint32_t ct_k = (((1000000 / device.ct) + 25) / 50) * 50; + WSContentSend_P(PSTR(" %dK"), device.ct, ct_k); + } + if (device.validHue() && device.validSat() && (channels >= 3)) { + uint8_t r,g,b; + uint8_t sat = changeUIntScale(device.sat, 0, 254, 0, 255); // scale to 0..255 + LightStateClass::HsToRgb(device.hue, sat, &r, &g, &b); + WSContentSend_P(PSTR(" #%02X%02X%02X"), r,g,b,r,g,b); + } else if (device.validX() && device.validY() && (channels >= 3)) { + uint8_t r,g,b; + LightStateClass::XyToRgb(device.x / 65535.0f, device.y / 65535.0f, &r, &g, &b); + WSContentSend_P(PSTR(" #%02X%02X%02X"), r,g,b,r,g,b); + } + } } WSContentSend_P(PSTR("{t}")); // Terminate current multi column table and open new table