diff --git a/CHANGELOG.md b/CHANGELOG.md index 9df16d77c..08d416270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,15 @@ All notable changes to this project will be documented in this file. - Matter support for split lights (`SetOption68 1` and `SetOption37 128`) (#21834) - Berry `webserver_async` (#21836) - NeoPool command `NPSetOption` to enabled/disable data validation/connection statistics (#21850) +- Analog GPIO ``ADC Input`` with ``AdcParam 1,,,,1`` provide direct light control +- Analog GPIO ``ADC Voltage`` with ``AdcParam 11,,,,`` provide energy monitoring with dc voltage +- Analog GPIO ``ADC Current`` with ``AdcParam 12,,,,`` provide energy monitoring with dc voltage ### Breaking Changed ### Changed - Berry consolidated constants for solidified classes reduces Flash size (#2185) -- Berry updated precompiled Windows binary +- Berry updated precompiled Windows binary (#21858) ### Fixed - Berry `light.get` for separate RGB/CT (#21818) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 102884e1d..7c4115293 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -126,6 +126,9 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - Support for Sonoff iFan04-H using template [#16402](https://github.com/arendst/Tasmota/issues/16402) - Support for Sonoff POWCT Ring [#21131](https://github.com/arendst/Tasmota/issues/21131) - Support for Wooliis Hall Effect Coulometer or Battery capacity monitor [#21732](https://github.com/arendst/Tasmota/issues/21732) +- Analog GPIO ``ADC Input`` with ``AdcParam 1,,,,1`` provide direct light control +- Analog GPIO ``ADC Voltage`` with ``AdcParam 11,,,,`` provide energy monitoring with dc voltage +- Analog GPIO ``ADC Current`` with ``AdcParam 12,,,,`` provide energy monitoring with dc voltage - Skip MQTT response if command is prefixed with underscore [#21740](https://github.com/arendst/Tasmota/issues/21740) - Skip MQTT response if commands are executed prefixed with ``Backlog2`` (no delay) or ``Backlog3`` [#21740](https://github.com/arendst/Tasmota/issues/21740) - Extend command ``SetOption147 1`` to disable publish of IRReceived MQTT messages [#21574](https://github.com/arendst/Tasmota/issues/21574) @@ -170,6 +173,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - ESP32 MI32 refactoring, bugfixes, generic device scanning [#21603](https://github.com/arendst/Tasmota/issues/21603) - ESP32 MI32 improve parser [#21648](https://github.com/arendst/Tasmota/issues/21648) - ESP32 TM1621 number overflow from "9999" to "12E3" [#21131](https://github.com/arendst/Tasmota/issues/21131) +- Berry updated precompiled Windows binary [#21858](https://github.com/arendst/Tasmota/issues/21858) - Berry simplified `module persist` [#21812](https://github.com/arendst/Tasmota/issues/21812) - Berry consolidated constants for solidified classes reduces Flash size [#2185](https://github.com/arendst/Tasmota/issues/2185) - Matter refactoring of bridged devices [#21575](https://github.com/arendst/Tasmota/issues/21575) diff --git a/tasmota/tasmota_xsns_sensor/xsns_02_analog.ino b/tasmota/tasmota_xsns_sensor/xsns_02_analog.ino index 85b0d116e..552374ada 100644 --- a/tasmota/tasmota_xsns_sensor/xsns_02_analog.ino +++ b/tasmota/tasmota_xsns_sensor/xsns_02_analog.ino @@ -34,7 +34,6 @@ #ifdef ESP8266 #define ANALOG_RESOLUTION 10 // 12 = 4095, 11 = 2047, 10 = 1023 #define ANALOG_RANGE 1023 // 4095 = 12, 2047 = 11, 1023 = 10 -#define ANALOG_PERCENT 10 // backward compatible div10 range #endif // ESP8266 #ifdef ESP32 @@ -42,8 +41,6 @@ #define ANALOG_RESOLUTION 12 // 12 = 4095, 11 = 2047, 10 = 1023 #undef ANALOG_RANGE #define ANALOG_RANGE 4095 // 4095 = 12, 2047 = 11, 1023 = 10 -#undef ANALOG_PERCENT -#define ANALOG_PERCENT ((ANALOG_RANGE + 50) / 100) // approximation to 1% ADC range #endif // ESP32 #define TO_CELSIUS(x) ((x) - 273.15f) @@ -212,52 +209,54 @@ void AdcGetSettings(uint32_t idx) { void AdcInitParams(uint8_t idx) { if ((Adcs.type != Adc[idx].type) || (Adc[idx].param1 > 1000000)) { - if (ADC_TEMP == Adc[idx].type) { - // Default Shelly 2.5 and 1PM parameters - Adc[idx].param1 = ANALOG_NTC_BRIDGE_RESISTANCE; - Adc[idx].param2 = ANALOG_NTC_RESISTANCE; - Adc[idx].param3 = ANALOG_NTC_B_COEFFICIENT * 10000; - Adc[idx].param4 = 0; // Default to Shelly mode with NTC towards GND - } - else if (ADC_LIGHT == Adc[idx].type) { - Adc[idx].param1 = ANALOG_LDR_BRIDGE_RESISTANCE; - Adc[idx].param2 = ANALOG_LDR_LUX_CALC_SCALAR; - Adc[idx].param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; - } - else if (ADC_RANGE == Adc[idx].type) { - Adc[idx].param1 = 0; - Adc[idx].param2 = ANALOG_RANGE; - Adc[idx].param3 = 0; - Adc[idx].param4 = 100; - } - else if (ADC_CT_POWER == Adc[idx].type) { - Adc[idx].param1 = ANALOG_CT_FLAGS; // (uint32_t) 0 - Adc[idx].param2 = ANALOG_CT_MULTIPLIER; // (uint32_t) 100000 - Adc[idx].param3 = ANALOG_CT_VOLTAGE; // (int) 10 - } - else if (ADC_PH == Adc[idx].type) { - Adc[idx].param1 = ANALOG_PH_CALSOLUTION_LOW_PH * ANALOG_PH_DECIMAL_MULTIPLIER; // PH of the calibration solution 1, which is the one with the lower PH - Adc[idx].param2 = ANALOG_PH_CALSOLUTION_LOW_ANALOG_VALUE; // Reading of AnalogInput while probe is in solution 1 - Adc[idx].param3 = ANALOG_PH_CALSOLUTION_HIGH_PH * ANALOG_PH_DECIMAL_MULTIPLIER; // PH of the calibration solution 2, which is the one with the higher PH - Adc[idx].param4 = ANALOG_PH_CALSOLUTION_HIGH_ANALOG_VALUE; // Reading of AnalogInput while probe is in solution 2 - } - else if (ADC_MQ == Adc[idx].type) { - Adc[idx].param1 = ANALOG_MQ_TYPE; // Could be MQ-002, MQ-004, MQ-131 .... - Adc[idx].param2 = (int)(ANALOG_MQ_A * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression - Adc[idx].param3 = (int)(ANALOG_MQ_B * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression - Adc[idx].param4 = (int)(ANALOG_MQ_RatioMQCleanAir * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression - } - else if (ADC_VOLTAGE == Adc[idx].type) { - Adc[idx].param1 = 0; - Adc[idx].param2 = ANALOG_RANGE; - Adc[idx].param3 = 0; - Adc[idx].param4 = ANALOG_V33 * 10000; - } - else if (ADC_CURRENT == Adc[idx].type) { - Adc[idx].param1 = 0; - Adc[idx].param2 = ANALOG_RANGE; - Adc[idx].param3 = 0; - Adc[idx].param4 = ANALOG_V33 * 10000; + switch (Adc[idx].type) { + case ADC_INPUT: + Adc[idx].param1 = 0; + Adc[idx].param2 = ANALOG_RANGE; + Adc[idx].param3 = 3; // Margin + Adc[idx].param4 = 0; // Default mode (0) or Direct mode (1) using Dimmer or Channel command + case ADC_TEMP: + // Default Shelly 2.5 and 1PM parameters + Adc[idx].param1 = ANALOG_NTC_BRIDGE_RESISTANCE; + Adc[idx].param2 = ANALOG_NTC_RESISTANCE; + Adc[idx].param3 = ANALOG_NTC_B_COEFFICIENT * 10000; + Adc[idx].param4 = 0; // Default to Shelly mode with NTC towards GND + break; + case ADC_LIGHT: + Adc[idx].param1 = ANALOG_LDR_BRIDGE_RESISTANCE; + Adc[idx].param2 = ANALOG_LDR_LUX_CALC_SCALAR; + Adc[idx].param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; + break; + case ADC_RANGE: + Adc[idx].param1 = 0; + Adc[idx].param2 = ANALOG_RANGE; + Adc[idx].param3 = 0; + Adc[idx].param4 = 100; + break; + case ADC_CT_POWER: + Adc[idx].param1 = ANALOG_CT_FLAGS; // (uint32_t) 0 + Adc[idx].param2 = ANALOG_CT_MULTIPLIER; // (uint32_t) 100000 + Adc[idx].param3 = ANALOG_CT_VOLTAGE; // (int) 10 + break; + case ADC_PH: + Adc[idx].param1 = ANALOG_PH_CALSOLUTION_LOW_PH * ANALOG_PH_DECIMAL_MULTIPLIER; // PH of the calibration solution 1, which is the one with the lower PH + Adc[idx].param2 = ANALOG_PH_CALSOLUTION_LOW_ANALOG_VALUE; // Reading of AnalogInput while probe is in solution 1 + Adc[idx].param3 = ANALOG_PH_CALSOLUTION_HIGH_PH * ANALOG_PH_DECIMAL_MULTIPLIER; // PH of the calibration solution 2, which is the one with the higher PH + Adc[idx].param4 = ANALOG_PH_CALSOLUTION_HIGH_ANALOG_VALUE; // Reading of AnalogInput while probe is in solution 2 + break; + case ADC_MQ: + Adc[idx].param1 = ANALOG_MQ_TYPE; // Could be MQ-002, MQ-004, MQ-131 .... + Adc[idx].param2 = (int)(ANALOG_MQ_A * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression + Adc[idx].param3 = (int)(ANALOG_MQ_B * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression + Adc[idx].param4 = (int)(ANALOG_MQ_RatioMQCleanAir * ANALOG_MQ_DECIMAL_MULTIPLIER); // Exponential regression + break; + case ADC_VOLTAGE: + case ADC_CURRENT: + Adc[idx].param1 = 0; + Adc[idx].param2 = ANALOG_RANGE; + Adc[idx].param3 = 0; + Adc[idx].param4 = ANALOG_V33 * 10000; + break; } } if ((Adcs.type != Adc[idx].type) || (0 == Adc[idx].param1) || (Adc[idx].param1 > ANALOG_RANGE)) { @@ -385,7 +384,6 @@ uint16_t AdcRead(uint32_t pin, uint32_t factor) { return analog; } -#ifdef USE_RULES void AdcEvery250ms(void) { char adc_idx[3] = { 0 }; uint32_t offset = 0; @@ -395,12 +393,35 @@ void AdcEvery250ms(void) { offset = 1; #endif if (ADC_INPUT == Adc[idx].type) { - uint16_t new_value = AdcRead(Adc[idx].pin, 5); - if ((new_value < Adc[idx].last_value -ANALOG_PERCENT) || (new_value > Adc[idx].last_value +ANALOG_PERCENT)) { + int adc = AdcRead(Adc[idx].pin, 4); // 4 = 16 mS + int new_value = changeUIntScale(adc, Adc[idx].param1, Adc[idx].param2, 0, 100); + if ((new_value < Adc[idx].last_value -Adc[idx].param3) || + (new_value > Adc[idx].last_value +Adc[idx].param3) || + ((0 == new_value) && (Adc[idx].last_value != 0)) || // Lowest end + ((100 == new_value) && (Adc[idx].last_value != 100))) { // Highest end Adc[idx].last_value = new_value; - uint16_t value = Adc[idx].last_value / ANALOG_PERCENT; - Response_P(PSTR("{\"ANALOG\":{\"A%ddiv10\":%d}}"), idx + offset, (value > 99) ? 100 : value); - XdrvRulesProcess(0); + if (-1 == Adc[idx].indexOfPointer) { + Adc[idx].indexOfPointer = 0; + continue; // Skip action on restart + } +#ifdef USE_LIGHT + if (0 == Adc[idx].param4) { // Default (0) or Direct mode (1) +#endif // USE_LIGHT + Response_P(PSTR("{\"ANALOG\":{\"A%ddiv10\":%d}}"), idx + offset, new_value); + XdrvRulesProcess(0); +#ifdef USE_LIGHT + } else { + char command[33]; + if (Settings->flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM +// snprintf_P(command, sizeof(command), PSTR("_" D_CMND_CHANNEL "%d %d"), idx +1, new_value); + snprintf_P(command, sizeof(command), PSTR(D_CMND_CHANNEL "%d %d"), idx +1, new_value); + } else { +// snprintf_P(command, sizeof(command), PSTR("_" D_CMND_DIMMER "3 %d"), new_value); + snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER "3 %d"), new_value); // Change both RGB and W(W) Dimmers with no fading + } + ExecuteCommand(command, SRC_SWITCH); + } +#endif // USE_LIGHT } } else if (ADC_JOY == Adc[idx].type) { @@ -416,7 +437,6 @@ void AdcEvery250ms(void) { } } } -#endif // USE_RULES uint8_t AdcGetButton(uint32_t pin) { for (uint32_t idx = 0; idx < Adcs.present; idx++) { @@ -641,16 +661,21 @@ void AdcShow(bool json) { switch (Adc[idx].type) { case ADC_INPUT: { - uint16_t analog = AdcRead(Adc[idx].pin, 5); - - if (json) { - AdcShowContinuation(&jsonflg); - ResponseAppend_P(PSTR("\"A%d\":%d"), idx + offset, analog); +#ifdef USE_LIGHT + if (0 == Adc[idx].param4) { // Default (0) or Direct mode (1) +#endif // USE_LIGHT + uint16_t analog = AdcRead(Adc[idx].pin, 5); + if (json) { + AdcShowContinuation(&jsonflg); + ResponseAppend_P(PSTR("\"A%d\":%d"), idx + offset, analog); #ifdef USE_WEBSERVER - } else { - WSContentSend_PD(HTTP_SNS_ANALOG, "", idx + offset, analog); + } else { + WSContentSend_PD(HTTP_SNS_ANALOG, "", idx + offset, analog); #endif // USE_WEBSERVER + } +#ifdef USE_LIGHT } +#endif // USE_LIGHT break; } case ADC_TEMP: { @@ -815,10 +840,11 @@ void CmndAdcParam(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_ADCS)) { uint8_t idx = XdrvMailbox.index -1; if (XdrvMailbox.data_len) { - if (XdrvMailbox.payload > ADC_INPUT) { + if (XdrvMailbox.payload > 0) { AdcGetSettings(idx); if (ArgC() > 3) { // Process parameter entry char argument[XdrvMailbox.data_len]; + // AdcParam 1, 0, ANALOG_RANGE, 0 ADC_INPUT rule | dimmer // AdcParam 2, 32000, 10000, 3350 ADC_TEMP Shelly mode // AdcParam 2, 32000, 10000, 3350, 1 ADC_TEMP Alternate mode // AdcParam 3, 10000, 12518931, -1.405 @@ -834,7 +860,8 @@ void CmndAdcParam(void) { Adc[idx].type = XdrvMailbox.payload; Adc[idx].param1 = strtol(ArgV(argument, 2), nullptr, 10); // param1 = int Adc[idx].param2 = strtol(ArgV(argument, 3), nullptr, 10); // param2 = int - if (ADC_RANGE == XdrvMailbox.payload) { + if ((ADC_INPUT == XdrvMailbox.payload) || + (ADC_RANGE == XdrvMailbox.payload)) { Adc[idx].param3 = abs(strtol(ArgV(argument, 4), nullptr, 10)); // param3 = abs(int) Adc[idx].param4 = abs(strtol(ArgV(argument, 5), nullptr, 10)); // param4 = abs(int) } else { @@ -897,13 +924,7 @@ void CmndAdcParam(void) { // AddLog(LOG_LEVEL_INFO, PSTR("ADC: MQ reset mq%d, a = %2_f, b = %2_f, ratioMQCleanAir = %2_f"), Adc[idx].param1, &a, &b, &ratioMQCleanAir); } } else { // Set default values based on current adc type - // AdcParam 2 - // AdcParam 3 - // AdcParam 4 - // AdcParam 5 - // AdcParam 6 - // AdcParam 7 - // AdcParam 8 + // AdcParam 1 Adcs.type = 0; AdcInitParams(idx); } @@ -914,7 +935,9 @@ void CmndAdcParam(void) { // AdcParam AdcGetSettings(idx); Response_P(PSTR("{\"" D_CMND_ADCPARAM "%d\":[%d,%d,%d"), idx +1, Adcs.type, Adc[idx].param1, Adc[idx].param2); - if ((ADC_RANGE == Adc[idx].type) || (ADC_MQ == Adc[idx].type)) { + if ((ADC_INPUT == Adc[idx].type) || + (ADC_RANGE == Adc[idx].type) || + (ADC_MQ == Adc[idx].type)) { ResponseAppend_P(PSTR(",%d,%d"), Adc[idx].param3, Adc[idx].param4); // param3 = int, param4 = int } else { @@ -980,11 +1003,9 @@ bool Xsns02(uint32_t function) { default: if (Adcs.present) { switch (function) { -#ifdef USE_RULES case FUNC_EVERY_250_MSECOND: AdcEvery250ms(); break; -#endif // USE_RULES case FUNC_EVERY_SECOND: AdcEverySecond(); break;