Tasmota/tasmota/tasmota_xsns_sensor/xsns_01_counter.ino
stefanbode 39abde2583
Worked on ESP32 dimmer with Zero cross (#18481)
* Worked on ESP32 dimmer with Zero cross

Until now the ESP32 does not support zero-cross dimmer. I take a sneak how they did in in ESPhome and adapted the approach to TASMOTA. At the end it works that smooth that likely i will change ESP8266 either so we have a common code. Currently ESP8266 is not touched.
There is a minor issue with savedata == default. When changing the dimmer value the interrupts get stopped during write of the config data to flash.

* ESP8266 Dimmer added

Worked all so well and the code is much smaller. There is no need for reconfiguration on existing users. But there are settings not needed anymore. Will work on the documentation. Anyhow existing installations can upgrade without hickup

* Optimized endpoints at dimmer 0 and 100

* Removed debug stuff

* Fix Issue at dimmer = 0

* Small bugfix

* Final checked Version

* Update xsns_01_counter.ino

* Add missing func

* Update xsns_01_counter.ino

* Moved out of the house of counter and build my own one

* New ZeroCross Driver

* Update xdrv_91_zerocrossDimmer.ino

* evolving

* Delete xdrv_91_zerocrossDimmer.ino

* Add files via upload

* Changed drv number from 1 to 68

* Commit to merge
2023-04-25 13:46:19 +02:00

316 lines
10 KiB
C++

/*
xsns_01_counter.ino - Counter sensors (water meters, electricity meters etc.) sensor support for Tasmota
Copyright (C) 2021 Maarten Damen and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_COUNTER
/*********************************************************************************************\
* Counter sensors (water meters, electricity meters etc.)
\*********************************************************************************************/
#define XSNS_01 1
#define D_PRFX_COUNTER "Counter"
#define D_CMND_COUNTERTYPE "Type"
#define D_CMND_COUNTERDEBOUNCE "Debounce"
#define D_CMND_COUNTERDEBOUNCELOW "DebounceLow"
#define D_CMND_COUNTERDEBOUNCEHIGH "DebounceHigh"
const char kCounterCommands[] PROGMEM = D_PRFX_COUNTER "|" // Prefix
"|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE "|" D_CMND_COUNTERDEBOUNCELOW "|" D_CMND_COUNTERDEBOUNCEHIGH ;
void (* const CounterCommand[])(void) PROGMEM = {
&CmndCounter, &CmndCounterType, &CmndCounterDebounce, &CmndCounterDebounceLow, &CmndCounterDebounceHigh };
uint8_t ctr_index[MAX_COUNTERS] = { 0, 1, 2, 3 };
struct COUNTER {
uint32_t timer[MAX_COUNTERS]; // Last counter time in micro seconds
uint32_t timer_low_high[MAX_COUNTERS]; // Last low/high counter time in micro seconds
uint8_t no_pullup = 0; // Counter input pullup flag (1 = No pullup)
uint8_t pin_state = 0; // LSB0..3 Last state of counter pin; LSB7==0 IRQ is FALLING, LSB7==1 IRQ is CHANGE
bool any_counter = false;
} Counter;
void IRAM_ATTR CounterIsrArg(void *arg) {
uint32_t index = *static_cast<uint8_t*>(arg);
uint32_t time = micros();
uint32_t debounce_time;
if (Counter.pin_state) {
// handle low and high debounce times when configured
if (digitalRead(Pin(GPIO_CNTR1, index)) == bitRead(Counter.pin_state, index)) {
// new pin state to be ignored because debounce time was not met during last IRQ
return;
}
debounce_time = time - Counter.timer_low_high[index];
if bitRead(Counter.pin_state, index) {
// last valid pin state was high, current pin state is low
if (debounce_time <= Settings->pulse_counter_debounce_high * 1000) return;
} else {
// last valid pin state was low, current pin state is high
if (debounce_time <= Settings->pulse_counter_debounce_low * 1000) return;
}
// passed debounce check, save pin state and timing
Counter.timer_low_high[index] = time;
Counter.pin_state ^= (1<<index);
// do not count on rising edge
if bitRead(Counter.pin_state, index) {
// PWMfrequency 100
// restart PWM each second (german 50Hz has to up to 0.01% deviation)
// restart initiated by setting Counter.startReSync = true;
#ifdef USE_AC_ZERO_CROSS_DIMMER
if (index == 3) ACDimmerZeroCross(time);
#endif //USE_AC_ZERO_CROSS_DIMMER
return;
}
}
debounce_time = time - Counter.timer[index];
if (debounce_time > Settings->pulse_counter_debounce * 1000) {
Counter.timer[index] = time;
if (bitRead(Settings->pulse_counter_type, index)) {
RtcSettings.pulse_counter[index] = debounce_time;
} else {
RtcSettings.pulse_counter[index]++;
}
}
}
/********************************************************************************************/
void CounterInterruptDisable(bool state)
{
if (state) { // Disable interrupts
if (Counter.any_counter) {
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (PinUsed(GPIO_CNTR1, i)) {
detachInterrupt(Pin(GPIO_CNTR1, i));
}
}
Counter.any_counter = false;
}
} else { // Enable interrupts
if (!Counter.any_counter) {
CounterInit();
}
}
}
bool CounterPinState(void)
{
if ((XdrvMailbox.index >= AGPIO(GPIO_CNTR1_NP)) && (XdrvMailbox.index < (AGPIO(GPIO_CNTR1_NP) + MAX_COUNTERS))) {
bitSet(Counter.no_pullup, XdrvMailbox.index - AGPIO(GPIO_CNTR1_NP));
XdrvMailbox.index -= (AGPIO(GPIO_CNTR1_NP) - AGPIO(GPIO_CNTR1));
return true;
}
return false;
}
void CounterInit(void)
{
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (PinUsed(GPIO_CNTR1, i)) {
Counter.any_counter = true;
pinMode(Pin(GPIO_CNTR1, i), bitRead(Counter.no_pullup, i) ? INPUT : INPUT_PULLUP);
if ((0 == Settings->pulse_counter_debounce_low) && (0 == Settings->pulse_counter_debounce_high) && !Settings->flag4.zerocross_dimmer) {
Counter.pin_state = 0;
attachInterruptArg(Pin(GPIO_CNTR1, i), CounterIsrArg, &ctr_index[i], FALLING);
} else {
Counter.pin_state = 0x8f;
attachInterruptArg(Pin(GPIO_CNTR1, i), CounterIsrArg, &ctr_index[i], CHANGE);
}
}
}
}
void CounterEverySecond(void)
{
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (PinUsed(GPIO_CNTR1, i)) {
if (bitRead(Settings->pulse_counter_type, i)) {
uint32_t time = micros() - Counter.timer[i];
if (time > 4200000000) { // 70 minutes
RtcSettings.pulse_counter[i] = 4200000000; // Set Timer to max in case of no more interrupts due to stall of measured device
}
}
}
}
}
void CounterSaveState(void)
{
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (PinUsed(GPIO_CNTR1, i)) {
Settings->pulse_counter[i] = RtcSettings.pulse_counter[i];
}
}
}
void CounterShow(bool json)
{
bool header = false;
uint8_t dsxflg = 0;
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (PinUsed(GPIO_CNTR1, i)) {
char counter[33];
if (bitRead(Settings->pulse_counter_type, i)) {
dtostrfd((double)RtcSettings.pulse_counter[i] / 1000000, 6, counter);
} else {
dsxflg++;
snprintf_P(counter, sizeof(counter), PSTR("%lu"), RtcSettings.pulse_counter[i]);
}
if (json) {
if (!header) {
ResponseAppend_P(PSTR(",\"COUNTER\":{"));
}
ResponseAppend_P(PSTR("%s\"C%d\":%s"), (header)?",":"", i +1, counter);
header = true;
#ifdef USE_DOMOTICZ
if ((0 == TasmotaGlobal.tele_period) && (1 == dsxflg)) {
DomoticzSensor(DZ_COUNT, RtcSettings.pulse_counter[i]);
dsxflg++;
}
#endif // USE_DOMOTICZ
if ((0 == TasmotaGlobal.tele_period ) && (Settings->flag3.counter_reset_on_tele)) {
RtcSettings.pulse_counter[i] = 0;
}
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(PSTR("{s}" D_COUNTER "%d{m}%s%s{e}"),
i +1, counter, (bitRead(Settings->pulse_counter_type, i)) ? " " D_UNIT_SECOND : "");
#endif // USE_WEBSERVER
}
}
}
if (header) {
ResponseJsonEnd();
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
void CmndCounter(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
if ((XdrvMailbox.data_len > 0) && PinUsed(GPIO_CNTR1, XdrvMailbox.index -1)) {
if ((XdrvMailbox.data[0] == '-') || (XdrvMailbox.data[0] == '+')) {
RtcSettings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload;
Settings->pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload;
} else {
RtcSettings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload;
Settings->pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload;
}
}
ResponseCmndIdxNumber(RtcSettings.pulse_counter[XdrvMailbox.index -1]);
}
}
void CmndCounterType(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1) && PinUsed(GPIO_CNTR1, XdrvMailbox.index -1)) {
bitWrite(Settings->pulse_counter_type, XdrvMailbox.index -1, XdrvMailbox.payload &1);
RtcSettings.pulse_counter[XdrvMailbox.index -1] = 0;
Settings->pulse_counter[XdrvMailbox.index -1] = 0;
}
ResponseCmndIdxNumber(bitRead(Settings->pulse_counter_type, XdrvMailbox.index -1));
}
}
void CmndCounterDebounce(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings->pulse_counter_debounce = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings->pulse_counter_debounce);
}
void CmndCounterDebounceLow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings->pulse_counter_debounce_low = XdrvMailbox.payload;
CounterInit();
}
ResponseCmndNumber(Settings->pulse_counter_debounce_low);
}
void CmndCounterDebounceHigh(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings->pulse_counter_debounce_high = XdrvMailbox.payload;
CounterInit();
}
ResponseCmndNumber(Settings->pulse_counter_debounce_high);
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns01(uint32_t function)
{
bool result = false;
if (Counter.any_counter) {
switch (function) {
case FUNC_EVERY_SECOND:
CounterEverySecond();
break;
case FUNC_JSON_APPEND:
CounterShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
CounterShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART:
case FUNC_SAVE_AT_MIDNIGHT:
CounterSaveState();
break;
case FUNC_COMMAND:
result = DecodeCommand(kCounterCommands, CounterCommand);
break;
case FUNC_INTERRUPT_STOP:
CounterInterruptDisable(true);
break;
case FUNC_INTERRUPT_START:
CounterInterruptDisable(false);
break;
}
} else {
switch (function) {
case FUNC_INIT:
CounterInit();
break;
case FUNC_PIN_STATE:
result = CounterPinState();
break;
}
}
return result;
}
#endif // USE_COUNTER