In short. ESP32, two water meters (hot and cold). Booth, hot and cold, some interrupts are missed. When i connect ESP32 to serial monitor and watch then all interrupts are detected (about 5-10 minutes). But if i leave to 5v power only, then some interrupts are missed (in one day it can be up to 50 or even more).
About water counter - one pulse per liter. Correct info about sensor is in PDF Original Operating Instructions.
Sensor connections. Brown wire to GND, white to PIN 32(hot),33(cold).
Board. Olimex ESP32-POE Rev C.
- Powered via 5v Samsung phone charger (standard 5V charger without 9v)
- WiFi enabled (For NTP and to see counter readings)
- OTA enabled
- WEB server running (plain html, ESPAsyncWebServer)
- Bluetooth disabled
- SD card attached (stores SQLite database only)
- Uses siara-cc/esp32_arduino_sqlite3_lib library
Water usage in last month (May)
- hot - 4777 liters (counted with sensor - 2675)
- cold - 9289, with sensor - 5528
As we can see, difference is huge. Maybe ISR routine exec time is too long and some interrupts are skipped? I know that WiFi too uses interrupts... Is my interrupt code wrong? Water counter reading is stored in SQLite database when 10 liters are used. Thanks for anyone for your time in advance!
Here is image of water counter and sensor
Here is some code, related to interrupts (debug disabled).
struct WaterCounter
{
const uint8_t PIN; // microcontroller pin...
volatile uint32_t liters_total; // max 4294967295
volatile uint16_t liters_in_session; // max 65535
volatile uint32_t session_started; // unix timestamp
volatile uint32_t session_last_pulse; // unix timestamp
const char display_name[5];
volatile unsigned long pulse_start;
volatile unsigned long pulse_stop;
volatile uint32_t isr_prev_time;
volatile uint32_t liters_last_written;
volatile uint32_t liters_used_today;
};
WaterCounter wc_hot = { PIN_WC_HOT, 0, 0, 0, 0, "Hott", 0, 0, 0, 0, 0 };
WaterCounter wc_cold = {PIN_WC_COLD, 0, 0, 0, 0, "Cold", 0, 0, 0, 0, 0 };
void IRAM_ATTR handle_wc(WaterCounter *wc)
{
uint32_t t = micros();
uint32_t dt = t - wc->isr_prev_time;
if (dt<1500) return;
wc->isr_prev_time = t;
if (wc->pulse_start == 0)
{
wc->pulse_start = millis();
#ifdef DEBUG_ISR
//Serial.printf("Started %s pulse!\n", wc->display_name);
ets_printf("Started %s pulse!\n", wc->display_name);
#endif
}else
{
wc->pulse_stop = millis();
unsigned long pulsew = wc->pulse_stop - wc->pulse_start;
wc->pulse_start = 0;
if (pulsew > 40 && pulsew < 55)
{
wc->liters_total++;
wc->liters_in_session++;
wc->liters_used_today++;
if (wc->session_started == 0)
{
wc->session_started = now;
}
wc->session_last_pulse = now;
}
#ifdef DEBUG_ISR
//Serial.printf("%s: pulse_width: %ld\n", wc->display_name, pulsew);
ets_printf("%s: pulse_width: %ld\n", wc->display_name, pulsew);
#endif
}
}
void IRAM_ATTR isr_wc_cold()
{
handle_wc(&wc_cold);
}
void IRAM_ATTR isr_wc_hot()
{
handle_wc(&wc_hot);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Boot...");
// allow some time to pull up pin, interrupt is attached below
pinMode(wc_cold.PIN, INPUT_PULLUP);
pinMode(wc_hot.PIN, INPUT_PULLUP);
Serial.printf("Watchdog timeout: %dms\n", WATCHDOG_TIMEOUT);
timerWD = timerBegin(0, 80, true); //timer 0, div 80
timerAttachInterrupt(timerWD, &resetModule, true); //attach callback
timerAlarmWrite(timerWD, WATCHDOG_TIMEOUT * 1000, false); //set time in us
timerAlarmEnable(timerWD); //enable interrupt
timerWrite(timerWD, 0); //reset timer (feed watchdog)
SD_MMC.begin();
enqRC = sqlite3_initialize();
if (enqRC == SQLITE_OK)
{
openDb();
initSensorDatabase();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
SetupWiFi();
closeDb();
}
timerWrite(timerWD, 0); //reset timer (feed watchdog)
pollNtp();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
openDb();
initTotalValues(&wc_cold);
timerWrite(timerWD, 0); //reset timer (feed watchdog)
initTotalValues(&wc_hot);
closeDb();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
attachInterrupt(digitalPinToInterrupt(wc_cold.PIN), &isr_wc_cold, FALLING);
attachInterrupt(digitalPinToInterrupt(wc_hot.PIN), &isr_wc_hot, FALLING);
timer_session = timerBegin(1, 80, true);
timerAttachInterrupt(timer_session, &onSessionTimer, true);
timerAlarmWrite(timer_session, 1000000, true); // every 1 second
timerAlarmEnable(timer_session);
EnableOTA();
//* async web
webServer.on("/", HTTP_GET, handleRootAsync);
webServer.on("/heap", HTTP_GET, heapInfoAsync);
webServer.on("/post", HTTP_POST, handlePostAsync);
webServer.on("/params", HTTP_GET, handleParameterPage);
webServer.onNotFound(onNotFoundAsync);
webServer.begin();
time(&now);
}
void loop() {
// put your main code here, to run repeatedly:
loop_task();
}
void loop_task()
{
if (time_every_second <= now)
{
writeWcTotalToDb(&wc_cold);
writeWcTotalToDb(&wc_hot);
time_every_second = now + 1;
}
if (time_every_30_seconds <= now)
{
pollNtp();
time_every_30_seconds = now + 30;
}
}
void IRAM_ATTR finishSession(WaterCounter *wc, time_t tt)
{
if (wc->session_started == 0)
{
return;
}
if (tt - wc->session_last_pulse > SESSION_LENGHT)
{
//finish session
#ifdef DEBUG_ISR
//Serial.printf("# %s liters in session: %d, duration %d(s)\n", wc->display_name, wc->liters_in_session, wc->session_last_pulse - wc->session_started);
ets_printf("# %s liters in session: %d, duration %d(s)\n", wc->display_name, wc->liters_in_session, wc->session_last_pulse - wc->session_started);
#endif
wc->session_started = 0;
wc->liters_in_session = 0;
}
}
/* every 1s */
void IRAM_ATTR onSessionTimer()
{
timerWrite(timerWD, 0); //reset timer (feed watchdog)
//ets_printf("\nonSessionTimer()\n");
time(&now);
getLocalTime(&timeinfo);
strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
if (now % 86400 == 75600) /* works if this function is called every second */
{
ets_printf("\n!!!midnight!!\n");
wc_cold.liters_used_today = 0;
wc_hot.liters_used_today = 0;
}
finishSession(&wc_cold, now);
finishSession(&wc_hot, now);
}
void writeWcTotalToDb(WaterCounter *wc)
{
#ifdef DEBUG_WRITE
Serial.printf("writeWcTotalToDb(%s): begin\n", wc->display_name);
#endif
uint8_t ldiff = wc->liters_total - wc->liters_last_written;
if (ldiff == 0)
{
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): diff zero");
#endif
return;
}
if (ldiff <= LITERS_BEFORE_WRITE && wc->session_started > 0)
{
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): ldiff <= LITERS_BEFORE_WRITE && wc->session_started > 0");
#endif
return;
}
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): start writting procedure!");
#endif
if (!openDb())
{
char *sql = "INSERT INTO `water_counter` VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
sqliteResultCode = sqlite3_prepare_v2(sqliteDb, sql, strlen(sql), &sqliteRes, NULL);
if (sqliteResultCode != SQLITE_OK)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): ERROR preparing sql: %d -> %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): Prepare OK\n");
#endif
//getLocalTime(&timeinfo); // inside every 1 sec
//strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
sqlite3_bind_int(sqliteRes, 1, now); // datums
sqlite3_bind_int(sqliteRes, 2, wc->liters_total);
sqlite3_bind_text(sqliteRes, 3, timeStringBuff, strlen(timeStringBuff), SQLITE_STATIC);
sqlite3_bind_text(sqliteRes, 4, wc->display_name, strlen(wc->display_name), SQLITE_STATIC);
sqlite3_bind_int(sqliteRes, 5, 0);
sqlite3_bind_int(sqliteRes, 6, wc->liters_in_session);
sqlite3_bind_int(sqliteRes, 7, wc->session_started);
sqlite3_bind_int(sqliteRes, 8, 0); /* currently not used */
if (sqliteResultCode = sqlite3_step(sqliteRes) != SQLITE_DONE)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): ERROR executing stmt: %d -> %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): data saved!\n");
#endif
wc->liters_last_written = wc->liters_total;
sqlite3_clear_bindings(sqliteRes);
sqliteResultCode = sqlite3_reset(sqliteRes);
if (sqliteResultCode != SQLITE_OK)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): sqlite3_reset(res) result code = [%s] %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): sqlite3_reset(res) OK\n");
#endif
}
sqlite3_finalize(sqliteRes);
}
}
closeDb();
}else
{
#ifdef DEBUG
Serial.println("writeWcTotalToDb(): Failed to open database!");
#endif
}
#ifdef DEBUG_WRITE
Serial.printf("writeWcTotalToDb(%s): finished!\n", wc->display_name);
#endif
} /* writeWcTotalToDb() */