/*
xsns_01_counter.ino - Counter sensors (water meters, electricity meters etc.) sensor support for Sonoff-Tasmota
Copyright (C) 2019 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 .
*/
#ifdef USE_COUNTER
/*********************************************************************************************\
* Counter sensors (water meters, electricity meters etc.)
\*********************************************************************************************/
#define XSNS_01 1
unsigned long last_counter_timer[MAX_COUNTERS]; // Last counter time in micro seconds
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 // Fix core 2.5.x ISR not in IRAM Exception
void CounterUpdate(uint8_t index) ICACHE_RAM_ATTR;
void CounterUpdate1(void) ICACHE_RAM_ATTR;
void CounterUpdate2(void) ICACHE_RAM_ATTR;
void CounterUpdate3(void) ICACHE_RAM_ATTR;
void CounterUpdate4(void) ICACHE_RAM_ATTR;
#endif // ARDUINO_ESP8266_RELEASE_2_3_0
void CounterUpdate(uint8_t index)
{
unsigned long counter_debounce_time = micros() - last_counter_timer[index -1];
if (counter_debounce_time > Settings.pulse_counter_debounce * 1000) {
last_counter_timer[index -1] = micros();
if (bitRead(Settings.pulse_counter_type, index -1)) {
RtcSettings.pulse_counter[index -1] = counter_debounce_time;
} else {
RtcSettings.pulse_counter[index -1]++;
}
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CNTR: Interrupt %d"), index);
}
}
void CounterUpdate1(void)
{
CounterUpdate(1);
}
void CounterUpdate2(void)
{
CounterUpdate(2);
}
void CounterUpdate3(void)
{
CounterUpdate(3);
}
void CounterUpdate4(void)
{
CounterUpdate(4);
}
/********************************************************************************************/
void CounterSaveState(void)
{
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (pin[GPIO_CNTR1 +i] < 99) {
Settings.pulse_counter[i] = RtcSettings.pulse_counter[i];
}
}
}
void CounterInit(void)
{
typedef void (*function) () ;
function counter_callbacks[] = { CounterUpdate1, CounterUpdate2, CounterUpdate3, CounterUpdate4 };
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (pin[GPIO_CNTR1 +i] < 99) {
pinMode(pin[GPIO_CNTR1 +i], bitRead(counter_no_pullup, i) ? INPUT : INPUT_PULLUP);
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING);
}
}
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_COUNTER[] PROGMEM =
"{s}" D_COUNTER "%d{m}%s%s{e}"; // {s} =
, {m} = | , {e} = |
#endif // USE_WEBSERVER
void CounterShow(bool json)
{
char stemp[10];
uint8_t dsxflg = 0;
uint8_t header = 0;
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
if (pin[GPIO_CNTR1 +i] < 99) {
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\":{"));
stemp[0] = '\0';
}
header++;
ResponseAppend_P(PSTR("%s\"C%d\":%s"), stemp, i +1, counter);
strlcpy(stemp, ",", sizeof(stemp));
#ifdef USE_DOMOTICZ
if ((0 == tele_period) && (1 == dsxflg)) {
DomoticzSensor(DZ_COUNT, RtcSettings.pulse_counter[i]);
dsxflg++;
}
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_COUNTER, i +1, counter, (bitRead(Settings.pulse_counter_type, i)) ? " " D_UNIT_SECOND : "");
#endif // USE_WEBSERVER
}
}
if (bitRead(Settings.pulse_counter_type, i)) {
RtcSettings.pulse_counter[i] = 0xFFFFFFFF; // Set Timer to max in case of no more interrupts due to stall of measured device
}
}
if (json) {
if (header) {
ResponseJsonEnd();
}
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
enum CounterCommands { CMND_COUNTER, CMND_COUNTERTYPE, CMND_COUNTERDEBOUNCE };
const char kCounterCommands[] PROGMEM = D_CMND_COUNTER "|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE ;
bool CounterCommand(void)
{
char command[CMDSZ];
bool serviced = true;
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kCounterCommands);
if (CMND_COUNTER == command_code) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
if ((XdrvMailbox.data_len > 0) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) {
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;
}
}
Response_P(S_JSON_COMMAND_INDEX_LVALUE, command, XdrvMailbox.index, RtcSettings.pulse_counter[XdrvMailbox.index -1]);
}
}
else if (CMND_COUNTERTYPE == command_code) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) {
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;
}
Response_P(S_JSON_COMMAND_INDEX_NVALUE, command, XdrvMailbox.index, bitRead(Settings.pulse_counter_type, XdrvMailbox.index -1));
}
}
else if (CMND_COUNTERDEBOUNCE == command_code) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
Settings.pulse_counter_debounce = XdrvMailbox.payload;
}
Response_P(S_JSON_COMMAND_NVALUE, command, Settings.pulse_counter_debounce);
}
else serviced = false; // Unknown command
return serviced;
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns01(uint8_t function)
{
bool result = false;
switch (function) {
case FUNC_INIT:
CounterInit();
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 = CounterCommand();
break;
}
return result;
}
#endif // USE_COUNTER