Closed VoyteckPL closed 1 year ago
Hi, The comments point this out:
//PIR motion sensor is connected to GPIO4 (Pin: D12)
#define PIR_GPIO 4
#define PIR_DEEPSLEEP_PIN GPIO_NUM_4
In general reading a reed-switch should also be able to trigger the wakeup. Depending on the number of times it wakes the processor, just counting the events and transmitting a few times a day might be required to achieve good runtime. This certainly requires several changes, but is certainly achievable.
Thanks. Is there any chance you could help me with the code? I can see you made a pro job here. I'm a total noob when it comes to arduino. I see there are some pir specific option like ignore after motion etc. I would be grateful if you could help.
@VoyteckPL : Sure, I am willing to assist, however I am not coding it for you. I am also interested in measuring my gasmeter with a reed switch or the ESP32 internal hall sensor. If you have specific questions I suggest you post code and we can discuss and tweak it until it works. If the result is indeed powersaving enough, that is something I can only imagine but not promise yet. It SHOULD be OK in my opinion.
Ok. When Firebeetle arrives I will do some testing. When I look at your code it seems to be good for reed switch with very little adjustments.
That's cool. I also ordered a REED switch to test it and am looking forward to collaborate.
Nice! My firebeetle should arrive tomorrow 🙂
I'm trying to compile the code but I get this error. Which exactly libraries did you use?
I have FireBeetle 2 board.
I also need to check with the current Arduino-IDE.
The working versions are:
Maybe some path have changed and need to be updated. I will also use the current IDE and see what might have changed...
Edit: added MQTT library info
Ok I will check and report back
It also compiles, uploads and send serial output with:
It should compile and run. I haven't entered my WiFi-credentials yet and a full test.
Fantastic. Thanks. I will do some testing today.
Yay! I was able to compile. The problem was I didn't have 2.0.6 Board Definition installed. I had to manually add this link to preferences :
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
By default it was only 1.0.6.
Ok so I sucessfully uploaded firmware! :) My reed sensor should be between pins GND and D12 (GPIO4) right? I won't be using that 3rd cable for 3.3V as I would for PIR sensor.
Yes, if you use D12 you won't have to lookup other #define
, which saves you some lookups.
To provide current to the sensor, a Pullup or Pulldown resistor must be actived. The Arduino function pinMode(PIR_GPIO, INPUT_PULLUP)
can be used. When enabling the internal pullup, you connect the reed-switch to D12 and GND.
The 3.3V is not required, right.
Thank you! Unfortunately I have no idea how to adjust your code :( I tried but C++ is too hard for me at the moment as I'm newbie.... I can only help with logic and experience as I tested similar solution with Zigbee, Tasmota and ESPHome and I noticed the things which are really important for this gas meter. I can support you somehow if you take it in account. I would also do some testing and report back.
I suppose that is all. What I also noticed > I have only gas heater: When water is heated (cycle which takes around 20 minutes) the usage is 0.01 m3 per ~15 seconds When heating is on (it can take longer periods of time for example few hours of constant but slow gas consumption) the usage is 0.01 m3 per ~ 90 seconds.
I'm really sorry I can't help with the coding. I'm sure this solution will become very popular. I tested zigbee and it was not reliable (no signal quality info, loosing packets)
Ok, no worries. Please chime back in anytime when you feel like picking up the project again and thank you for the hints, surely it helps when working on it again.
No problem. If I could support you some other way - I'm open 😉 we can talk on WhatsApp if you wish.
To read the Reed-switch and light the LED accordingly:
const int ReedPin = 4;
const int ledPin = 2;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(ReedPin, INPUT_PULLUP);
}
void loop() {
digitalWrite(ledPin, digitalRead(ReedPin));
}
To read the reed-switch and debounce with a second:
/*******************************************************************************
# #
# Using the Firebeetle 2 ESP32-E as battery powered gasmeter-reader #
# Project: https://github.com/Torxgewinde/Firebeetle-2-ESP32-E #
# #
# Firebeetle documentation at: #
# https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654 # #
# #
# Copyright (C) 2022 Tom Stöveken #
# #
# 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; version 2 of the License. #
# #
# 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, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
********************************************************************************/
#include <WiFi.h>
#include <MQTT.h>
#include "esp_adc_cal.h"
#define ESSID "WIFI SSID"
#define PSK "WIFI PASSWORD"
#define LOW_BATTERY_VOLTAGE 3.20
#define VERY_LOW_BATTERY_VOLTAGE 3.10
#define CRITICALLY_LOW_BATTERY_VOLTAGE 3.00
//char *MQTTServer = "server.lan";
IPAddress MQTTServer = IPAddress(192,168,1,1);
uint16_t MQTTPort = 1883;
String MQTTUsername = "username";
String MQTTPassword = "password";
String MQTTDeviceName = "Gaszaehler";
String MQTTRootTopic = "Keller/Gaszaehler";
enum _state {
S_STARTUP = 0,
S_DEBOUNCE_LOW,
S_DEBOUNCE_HIGH,
S_IDLE
};
enum _message {
M_COUNTER = 0,
M_STATUS
};
RTC_NOINIT_ATTR struct {
uint8_t bssid[6];
uint8_t channel;
float BatteryVoltage; //battery voltage in V
uint64_t NumberOfRestarts; //number of restarts
uint64_t ActiveTime; //time being active in ms
enum _state state; //keep track of current state
uint64_t counter;
} cache;
//REED contact is connected to GPIO4 (Pin: D12)
#define REED_GPIO 4
#define REED_DEEPSLEEP_PIN GPIO_NUM_4
#define DEBOUNCE_TIME 1*1000000ULL
//periodically wakeup and report battery status even without motion
#define LONG_TIME 12*60*60*1000000ULL
/******************************************************************************
Description.: bring the WiFi up
Input Value.: When "tryCachedValuesFirst" is true the function tries to use
cached values before attempting a scan + association
Return Value: true if WiFi is up, false if it timed out
******************************************************************************/
bool WiFiUP(bool tryCachedValuesFirst) {
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
if(tryCachedValuesFirst && cache.channel > 0) {
Serial.printf("Cached values as follows:\r\n");
Serial.printf(" Channel....: %d\r\n", cache.channel);
Serial.printf(" BSSID......: %x:%x:%x:%x:%x:%x\r\n", cache.bssid[0], \
cache.bssid[1], \
cache.bssid[2], \
cache.bssid[3], \
cache.bssid[4], \
cache.bssid[5]);
WiFi.begin(ESSID, PSK, cache.channel, cache.bssid);
for (unsigned long i=millis(); millis()-i < 10000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected with cached values (%lu)\r\n", millis()-i);
return true;
}
}
}
cache.channel = 0;
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = 0;
// try it with the slower process
WiFi.begin(ESSID, PSK);
for (unsigned long i=millis(); millis()-i < 60000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected (%lu)\r\n", millis()-i);
uint8_t *bssid = WiFi.BSSID();
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = bssid[i];
cache.channel = WiFi.channel();
return true;
}
}
Serial.printf("WiFi NOT connected\r\n");
return false;
}
/******************************************************************************
Description.: reads the battery voltage through the voltage divider at GPIO34
if the ESP32-E has calibration eFused those will be used.
In comparison with a regular voltmeter the values of ESP32 and
multimeter differ only about 0.05V
Input Value.: -
Return Value: battery voltage in volts
******************************************************************************/
float readBattery() {
uint32_t value = 0;
int rounds = 11;
esp_adc_cal_characteristics_t adc_chars;
//battery voltage divided by 2 can be measured at GPIO34, which equals ADC1_CHANNEL6
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
switch(esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars)) {
case ESP_ADC_CAL_VAL_EFUSE_TP:
Serial.println("Characterized using Two Point Value");
break;
case ESP_ADC_CAL_VAL_EFUSE_VREF:
Serial.printf("Characterized using eFuse Vref (%d mV)\r\n", adc_chars.vref);
break;
default:
Serial.printf("Characterized using Default Vref (%d mV)\r\n", 1100);
}
//to avoid noise, sample the pin several times and average the result
for(int i=1; i<=rounds; i++) {
value += adc1_get_raw(ADC1_CHANNEL_6);
}
value /= (uint32_t)rounds;
//due to the voltage divider (1M+1M) values must be multiplied by 2
//and convert mV to V
return esp_adc_cal_raw_to_voltage(value, &adc_chars)*2.0/1000.0;
}
/******************************************************************************
Description.: send MQTT message
Input Value.: msg selects the message to send
Return Value: true if OK, false if errors occured
******************************************************************************/
bool SendMessage(enum _message msg) {
char buf[256] = {0};
//read RTC
struct timeval tv;
gettimeofday(&tv, NULL);
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient;
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str())) {
switch(msg) {
case M_COUNTER:
Serial.printf("Sending counter: %d\r\n", cache.counter);
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), false, 2);
break;
case M_STATUS:
Serial.printf("Sending status\r\n");
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), false, 2);
MQTTClient.publish(MQTTRootTopic+"/BatteryVoltage", String(cache.BatteryVoltage, 3), false, 2);
snprintf(buf, sizeof(buf)-1, "%ld.%06ld", tv.tv_sec, tv.tv_usec);
MQTTClient.publish(MQTTRootTopic+"/BatteryRuntime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.NumberOfRestarts);
MQTTClient.publish(MQTTRootTopic+"/Restarts", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.ActiveTime);
MQTTClient.publish(MQTTRootTopic+"/ActiveTime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%ld", WiFi.RSSI());
MQTTClient.publish(MQTTRootTopic+"/RSSI", buf, false, 2);
break;
default:
Serial.printf("unkown message, not sending anything\r\n");
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: since this is a battery sensor, everything happens in setup
and when the tonguing' is done the device enters deep-sleep
Input Value.: -
Return Value: -
******************************************************************************/
void setup() {
//visual feedback when we are active, turn on onboard LED
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
cache.NumberOfRestarts++;
Serial.begin(115200);
Serial.print("===================================================\r\n");
Serial.printf("FireBeetle active\r\n" \
" Compiled at: " __DATE__ " - " __TIME__ "\r\n" \
" ESP-IDF: %s\r\n", esp_get_idf_version());
//read battery voltage
cache.BatteryVoltage = readBattery();
Serial.printf("Voltage: %4.3f V\r\n", cache.BatteryVoltage);
//a reset is required to wakeup again from below CRITICALLY_LOW_BATTERY_VOLTAGE
//this is to prevent damaging the empty battery by saving as much power as possible
if (cache.BatteryVoltage < CRITICALLY_LOW_BATTERY_VOLTAGE) {
Serial.println("Battery critically low, hibernating...");
//switch off everything that might consume power
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_CPU, ESP_PD_OPTION_OFF);
//disable all wakeup sources
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//if battery is below LOW_BATTERY_VOLTAGE but still above CRITICALLY_LOW_BATTERY_VOLTAGE,
//stop doing the regular work
//when put on charge the device will wakeup after a while and recognize voltage is OK
//this way the battery can run low, put still wakeup without physical interaction
if (cache.BatteryVoltage < LOW_BATTERY_VOLTAGE) {
Serial.println("Battery low, deep sleeping...");
//sleep ~60 minutes if battery is CRITICALLY_LOW_BATTERY_VOLTAGE to VERY_LOW_BATTERY_VOLTAGE
//sleep ~10 minutes if battery is VERY_LOW_BATTERY_VOLTAGE to LOW_BATTERY_VOLTAGE
uint64_t sleeptime = (cache.BatteryVoltage >= VERY_LOW_BATTERY_VOLTAGE) ? \
10*60*1000000ULL : 60*60*1000000ULL;
esp_sleep_enable_timer_wakeup(sleeptime);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//read GPIO level
pinMode(REED_GPIO, INPUT_PULLUP);
int level = digitalRead(REED_GPIO);
Serial.printf("Reed-switch is: %s\r\n", (level)?"NOMAGNET":"MAGNET");
//check if a reset/power-on occured
if (esp_reset_reason() == ESP_RST_POWERON) {
Serial.printf("ESP was just switched ON\r\n");
cache.state = S_STARTUP;
cache.ActiveTime = 0;
cache.NumberOfRestarts = 0;
cache.counter = 0;
//set RTC to 0
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
//default is to have WiFi off
if (WiFi.getMode() != WIFI_OFF) {
Serial.printf("WiFi wasn't off!\r\n");
WiFi.persistent(true);
WiFi.mode(WIFI_OFF);
}
//try to connect
WiFiUP(false);
WiFi.disconnect(true, true);
//transition to new state
cache.state = (level == HIGH) ? S_DEBOUNCE_HIGH : S_DEBOUNCE_LOW;
Serial.printf("transition to state nr.: %d\r\n", cache.state);
//wake by timer after debounce time is up
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
}
// check if ESP returns from deepsleep
if (esp_reset_reason() == ESP_RST_DEEPSLEEP) {
switch(esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.printf("ESP woke up due to timer\r\n");
switch(cache.state) {
case S_DEBOUNCE_LOW:
case S_DEBOUNCE_HIGH:
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
cache.counter++;
WiFiUP(true);
SendMessage(M_COUNTER);
WiFi.disconnect(true, true);
}
Serial.printf("transition to state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
case S_IDLE:
WiFiUP(true);
SendMessage(M_STATUS);
WiFi.disconnect(true, true);
Serial.printf("remaining in state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
default:
Serial.printf("ESP woke up by timer in an unknown state\r\n");
}
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.printf("ESP woke up by EXT0\r\n");
if (level == HIGH) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("transition to state S_DEBOUNCE_HIGH\r\n");
}
else {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("transition to state S_DEBOUNCE_LOW\r\n");
}
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
break;
default:
Serial.printf("ESP woke up due to an unknown reason\r\n");
}
}
Serial.printf("counter: %llu\r\n", cache.counter);
Serial.printf("=== entering deepsleep after %d ms ===\r\n.\r\n.\r\n.\r\n", millis());
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
}
void loop() {
Serial.println("This should never get printed");
}
It is not done yet, but this is what I currently have. Providing the gasmeter start-value is still missing...
Amazing work! Let me know if I can buy you a coffee somehow!
One question : in this example if gas meter stops or OPEN or CLOSED position will it always go to deep sleep?
The idea is, that it wakes up by changes to the reed-state. Only if the magnet is present (trigger) and still present after a second (for debounce) it increments the counter and transmits the counter via MQTT. To conserve battery it sleeps most of the time.
What is missing to wakeup every N-counts or every N-hours to transmit the whole status and the offset to start counting from. It is work in progress and not done yet!
This is very promising! Just uploaded and tested and speed is amazing. Around 2 seconds maximum for connection and send mqtt! The only problem I noticed so far is reset of counter after lost power and I only see counter in MQTT (no other values like voltage etc.)
Just one more clue ;) Usually one revolution of gas meter is 0.01 m3. It would be nice to have it with the decimals as result in MQTT rather than converting in from 1 to 0.01 in frontend.
Yes, i changed "counter" to be a "retained" value now. That way the broker will store the value even when resetting the ESP32 or changing the battery.
The new behavior will be:
mosquitto_pub -t "Keller/Gaszaehler/counter" -h server.lan -u "username" -P "password" -m "12345" -r
. Regardless who put the last value, the value as in the MQTT-broker will be incremented. from then on. The ESP32 will only check for this value after a reset or when power-cycling it.Conversion of counts to actual qubic-meters m³ might follow. I am happy if battery runtime can be checked and is acceptable - that is my main concern for now.
/*******************************************************************************
# #
# Using the Firebeetle 2 ESP32-E as battery powered gasmeter-reader #
# Project: https://github.com/Torxgewinde/Firebeetle-2-ESP32-E #
# #
# Firebeetle documentation at: #
# https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654 # #
# #
# Copyright (C) 2022 Tom Stöveken #
# #
# 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; version 2 of the License. #
# #
# 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, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
********************************************************************************/
#include <WiFi.h>
#include <MQTT.h>
#include <stdlib.h>
#include "esp_adc_cal.h"
#define ESSID ""
#define PSK ""
#define LOW_BATTERY_VOLTAGE 3.20
#define VERY_LOW_BATTERY_VOLTAGE 3.10
#define CRITICALLY_LOW_BATTERY_VOLTAGE 3.00
//char *MQTTServer = "server.lan";
IPAddress MQTTServer = IPAddress(192,168,1,1);
uint16_t MQTTPort = 1883;
String MQTTUsername = "";
String MQTTPassword = "";
String MQTTDeviceName = "Gaszaehler";
String MQTTRootTopic = "Keller/Gaszaehler";
enum _state {
S_STARTUP = 0,
S_DEBOUNCE_LOW,
S_DEBOUNCE_HIGH,
S_IDLE
};
enum _message {
M_COUNTER = 0,
M_STATUS
};
RTC_NOINIT_ATTR struct {
uint8_t bssid[6];
uint8_t channel;
float BatteryVoltage; //battery voltage in V
uint64_t NumberOfRestarts; //number of restarts
uint64_t ActiveTime; //time being active in ms
enum _state state; //keep track of current state
uint64_t counter; //gasmeter-counter
} cache;
//REED contact is connected to GPIO4 (Pin: D12)
#define REED_GPIO 4
#define REED_DEEPSLEEP_PIN GPIO_NUM_4
#define DEBOUNCE_TIME 1*1000000ULL
//periodically wakeup and report battery status
#define LONG_TIME 6*60*60*1000000ULL
/******************************************************************************
Description.: bring the WiFi up
Input Value.: When "tryCachedValuesFirst" is true the function tries to use
cached values before attempting a scan + association
Return Value: true if WiFi is up, false if it timed out
******************************************************************************/
bool WiFiUP(bool tryCachedValuesFirst) {
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
if(tryCachedValuesFirst && cache.channel > 0) {
Serial.printf("Cached values as follows:\r\n");
Serial.printf(" Channel....: %d\r\n", cache.channel);
Serial.printf(" BSSID......: %x:%x:%x:%x:%x:%x\r\n", cache.bssid[0], \
cache.bssid[1], \
cache.bssid[2], \
cache.bssid[3], \
cache.bssid[4], \
cache.bssid[5]);
WiFi.begin(ESSID, PSK, cache.channel, cache.bssid);
for (unsigned long i=millis(); millis()-i < 10000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected with cached values (%lu)\r\n", millis()-i);
return true;
}
}
}
cache.channel = 0;
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = 0;
// try it with the slower process
WiFi.begin(ESSID, PSK);
for (unsigned long i=millis(); millis()-i < 60000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected (%lu)\r\n", millis()-i);
uint8_t *bssid = WiFi.BSSID();
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = bssid[i];
cache.channel = WiFi.channel();
return true;
}
}
Serial.printf("WiFi NOT connected\r\n");
return false;
}
/******************************************************************************
Description.: reads the battery voltage through the voltage divider at GPIO34
if the ESP32-E has calibration eFused those will be used.
In comparison with a regular voltmeter the values of ESP32 and
multimeter differ only about 0.05V
Input Value.: -
Return Value: battery voltage in volts
******************************************************************************/
float readBattery() {
uint32_t value = 0;
int rounds = 11;
esp_adc_cal_characteristics_t adc_chars;
//battery voltage divided by 2 can be measured at GPIO34, which equals ADC1_CHANNEL6
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
switch(esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars)) {
case ESP_ADC_CAL_VAL_EFUSE_TP:
Serial.println("Characterized using Two Point Value");
break;
case ESP_ADC_CAL_VAL_EFUSE_VREF:
Serial.printf("Characterized using eFuse Vref (%d mV)\r\n", adc_chars.vref);
break;
default:
Serial.printf("Characterized using Default Vref (%d mV)\r\n", 1100);
}
//to avoid noise, sample the pin several times and average the result
for(int i=1; i<=rounds; i++) {
value += adc1_get_raw(ADC1_CHANNEL_6);
}
value /= (uint32_t)rounds;
//due to the voltage divider (1M+1M) values must be multiplied by 2
//and convert mV to V
return esp_adc_cal_raw_to_voltage(value, &adc_chars)*2.0/1000.0;
}
/******************************************************************************
Description.: send MQTT message
Input Value.: msg selects the message to send
Return Value: true if OK, false if errors occured
******************************************************************************/
bool SendMessage(enum _message msg) {
char buf[256] = {0};
//read RTC
struct timeval tv;
gettimeofday(&tv, NULL);
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str())) {
switch(msg) {
case M_COUNTER:
Serial.printf("Sending counter: %d\r\n", cache.counter);
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
break;
case M_STATUS:
Serial.printf("Sending status\r\n");
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
MQTTClient.publish(MQTTRootTopic+"/BatteryVoltage", String(cache.BatteryVoltage, 3), false, 2);
snprintf(buf, sizeof(buf)-1, "%ld.%06ld", tv.tv_sec, tv.tv_usec);
MQTTClient.publish(MQTTRootTopic+"/BatteryRuntime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.NumberOfRestarts);
MQTTClient.publish(MQTTRootTopic+"/Restarts", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.ActiveTime);
MQTTClient.publish(MQTTRootTopic+"/ActiveTime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%ld", WiFi.RSSI());
MQTTClient.publish(MQTTRootTopic+"/RSSI", buf, false, 2);
break;
default:
Serial.printf("unkown message, not sending anything\r\n");
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: get the counter from MQTT, if it is retained we use it as start
Input Value.: timeout in ms
Return Value: true if OK, false if errors occured
******************************************************************************/
bool GetCounter(unsigned int timeout) {
char buf[256] = {0};
bool gotCounter = false;
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
//callback as lambda-function, capture gotCounter by reference to signal when done
MQTTClient.onMessage((MQTTClientCallbackSimpleFunction)([&gotCounter](String &topic, String &payload) -> void {
Serial.println("Received MQTT message: " + topic + " - " + payload);
if ( topic == MQTTRootTopic+"/counter") {
cache.counter = strtoull(payload.c_str(), NULL, 10);
gotCounter = true;
}
return;
}));
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str()) ) {
MQTTClient.subscribe(MQTTRootTopic+"/counter");
for (int i=0; i<=timeout; i++) {
MQTTClient.loop();
if(gotCounter) {
Serial.println("received counter, will use it");
break;
}
usleep(1000);
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: since this is a battery sensor, everything happens in setup
and when the tonguing' is done the device enters deep-sleep
Input Value.: -
Return Value: -
******************************************************************************/
void setup() {
//visual feedback when we are active, turn on onboard LED
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
cache.NumberOfRestarts++;
Serial.begin(115200);
Serial.print("===================================================\r\n");
Serial.printf("FireBeetle active\r\n" \
" Compiled at: " __DATE__ " - " __TIME__ "\r\n" \
" ESP-IDF: %s\r\n", esp_get_idf_version());
//read battery voltage
cache.BatteryVoltage = readBattery();
Serial.printf("Voltage: %4.3f V\r\n", cache.BatteryVoltage);
//a reset is required to wakeup again from below CRITICALLY_LOW_BATTERY_VOLTAGE
//this is to prevent damaging the empty battery by saving as much power as possible
if (cache.BatteryVoltage < CRITICALLY_LOW_BATTERY_VOLTAGE) {
Serial.println("Battery critically low, hibernating...");
//switch off everything that might consume power
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_CPU, ESP_PD_OPTION_OFF);
//disable all wakeup sources
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//if battery is below LOW_BATTERY_VOLTAGE but still above CRITICALLY_LOW_BATTERY_VOLTAGE,
//stop doing the regular work
//when put on charge the device will wakeup after a while and recognize voltage is OK
//this way the battery can run low, put still wakeup without physical interaction
if (cache.BatteryVoltage < LOW_BATTERY_VOLTAGE) {
Serial.println("Battery low, deep sleeping...");
//sleep ~60 minutes if battery is CRITICALLY_LOW_BATTERY_VOLTAGE to VERY_LOW_BATTERY_VOLTAGE
//sleep ~10 minutes if battery is VERY_LOW_BATTERY_VOLTAGE to LOW_BATTERY_VOLTAGE
uint64_t sleeptime = (cache.BatteryVoltage >= VERY_LOW_BATTERY_VOLTAGE) ? \
10*60*1000000ULL : 60*60*1000000ULL;
esp_sleep_enable_timer_wakeup(sleeptime);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//read GPIO level
pinMode(REED_GPIO, INPUT_PULLUP);
int level = digitalRead(REED_GPIO);
Serial.printf("Reed-switch is: %s\r\n", (level)?"NOMAGNET":"MAGNET");
//check if a reset/power-on occured
if (esp_reset_reason() == ESP_RST_POWERON) {
Serial.printf("ESP was just switched ON\r\n");
cache.state = S_STARTUP;
cache.ActiveTime = 0;
cache.NumberOfRestarts = 0;
cache.counter = 0;
//set RTC to 0
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
//default is to have WiFi off
if (WiFi.getMode() != WIFI_OFF) {
Serial.printf("WiFi wasn't off!\r\n");
WiFi.persistent(true);
WiFi.mode(WIFI_OFF);
}
//try to connect to WiFi
WiFiUP(false);
//retrieve the previous gasmeter-counter, will return quickly if it is retained
GetCounter(10*1000);
WiFi.disconnect(true, true);
//transition to new state
cache.state = (level == HIGH) ? S_DEBOUNCE_HIGH : S_DEBOUNCE_LOW;
Serial.printf("transition to state nr.: %d\r\n", cache.state);
//wake by timer after debounce time is up
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
}
// check if ESP returns from deepsleep
if (esp_reset_reason() == ESP_RST_DEEPSLEEP) {
switch(esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.printf("ESP woke up due to timer\r\n");
switch(cache.state) {
case S_DEBOUNCE_LOW:
case S_DEBOUNCE_HIGH:
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
cache.counter++;
WiFiUP(true);
SendMessage(M_COUNTER);
WiFi.disconnect(true, true);
}
Serial.printf("transition to state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
case S_IDLE:
WiFiUP(true);
SendMessage(M_STATUS);
WiFi.disconnect(true, true);
Serial.printf("remaining in state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
default:
Serial.printf("ESP woke up by timer in an unknown state\r\n");
}
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.printf("ESP woke up by EXT0\r\n");
if (level == HIGH) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("transition to state S_DEBOUNCE_HIGH\r\n");
}
else {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("transition to state S_DEBOUNCE_LOW\r\n");
}
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
break;
default:
Serial.printf("ESP woke up due to an unknown reason\r\n");
}
}
Serial.printf("counter: %llu\r\n", cache.counter);
Serial.printf("=== entering deepsleep after %d ms ===\r\n", millis());
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
}
void loop() {
Serial.println("This should never get printed");
}
Damn, it is fast!
Still not sending full MQTT data (voltage etc.) ;) I had to at this:
Also one bug - when power is lost and reed switch is in MAGNET state 1 unit gets added to counter.
I also wonder what happends when MQTT server will be down (due to electricity failure). What will happen with retain message with current counter value. Wouldn't it be better to store it in EEPROM?
Not sending the full details is to send as little as possible (to preserve battery). I implemented now:
The slight error after power-on is not worth the effort to get rid of it. Replacing the battery (hopefully) happens once or twice per year and miscounting by one under certain circumstances is not worth the effort IMHO.
If the MQTT-broker gets down the ESP32 is still keeping the counter. The ESP32 only "learns" the retained value at first-start (=power-cycle or reset). Every time the ESP32 transmits the counter, it is now "retained", thus refreshed from the ESP32. To really loose the broker value and the ESP32, both must be restarted at more or less the same time. Since I plan to have the ESP32 running from battery, this would be a rare coincidence --> Acceptable IMHO.
/*******************************************************************************
# #
# Using the Firebeetle 2 ESP32-E as battery powered gasmeter-reader #
# Project: https://github.com/Torxgewinde/Firebeetle-2-ESP32-E #
# #
# Firebeetle documentation at: #
# https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654 # #
# #
# Copyright (C) 2022 Tom Stöveken #
# #
# 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; version 2 of the License. #
# #
# 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, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
********************************************************************************/
#include <WiFi.h>
#include <MQTT.h>
#include <stdlib.h>
#include "esp_adc_cal.h"
#define ESSID ""
#define PSK ""
#define LOW_BATTERY_VOLTAGE 3.20
#define VERY_LOW_BATTERY_VOLTAGE 3.10
#define CRITICALLY_LOW_BATTERY_VOLTAGE 3.00
//char *MQTTServer = "server.lan";
IPAddress MQTTServer = IPAddress(192,168,1,1);
uint16_t MQTTPort = 1883;
String MQTTUsername = "";
String MQTTPassword = "";
String MQTTDeviceName = "Gaszaehler";
String MQTTRootTopic = "Keller/Gaszaehler";
enum _state {
S_STARTUP = 0,
S_DEBOUNCE_LOW,
S_DEBOUNCE_HIGH,
S_IDLE
};
enum _message {
M_COUNTER = 0,
M_STATUS
};
RTC_NOINIT_ATTR struct {
uint8_t bssid[6];
uint8_t channel;
float BatteryVoltage; //battery voltage in V
uint64_t NumberOfRestarts; //number of restarts
uint64_t ActiveTime; //time being active in ms
enum _state state; //keep track of current state
uint64_t counter; //gasmeter-counter
} cache;
//REED contact is connected to GPIO4 (Pin: D12)
#define REED_GPIO 4
#define REED_DEEPSLEEP_PIN GPIO_NUM_4
#define DEBOUNCE_TIME 1*1000000ULL
//periodically wakeup and report battery status
#define LONG_TIME 60*60*1000000ULL
/******************************************************************************
Description.: bring the WiFi up
Input Value.: When "tryCachedValuesFirst" is true the function tries to use
cached values before attempting a scan + association
Return Value: true if WiFi is up, false if it timed out
******************************************************************************/
bool WiFiUP(bool tryCachedValuesFirst) {
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
if(tryCachedValuesFirst && cache.channel > 0) {
Serial.printf("Cached values as follows:\r\n");
Serial.printf(" Channel....: %d\r\n", cache.channel);
Serial.printf(" BSSID......: %x:%x:%x:%x:%x:%x\r\n", cache.bssid[0], \
cache.bssid[1], \
cache.bssid[2], \
cache.bssid[3], \
cache.bssid[4], \
cache.bssid[5]);
WiFi.begin(ESSID, PSK, cache.channel, cache.bssid);
for (unsigned long i=millis(); millis()-i < 10000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected with cached values (%lu)\r\n", millis()-i);
return true;
}
}
}
cache.channel = 0;
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = 0;
// try it with the slower process
WiFi.begin(ESSID, PSK);
for (unsigned long i=millis(); millis()-i < 60000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected (%lu)\r\n", millis()-i);
uint8_t *bssid = WiFi.BSSID();
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = bssid[i];
cache.channel = WiFi.channel();
return true;
}
}
Serial.printf("WiFi NOT connected\r\n");
return false;
}
/******************************************************************************
Description.: reads the battery voltage through the voltage divider at GPIO34
if the ESP32-E has calibration eFused those will be used.
In comparison with a regular voltmeter the values of ESP32 and
multimeter differ only about 0.05V
Input Value.: -
Return Value: battery voltage in volts
******************************************************************************/
float readBattery() {
uint32_t value = 0;
int rounds = 11;
esp_adc_cal_characteristics_t adc_chars;
//battery voltage divided by 2 can be measured at GPIO34, which equals ADC1_CHANNEL6
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
switch(esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars)) {
case ESP_ADC_CAL_VAL_EFUSE_TP:
Serial.println("Characterized using Two Point Value");
break;
case ESP_ADC_CAL_VAL_EFUSE_VREF:
Serial.printf("Characterized using eFuse Vref (%d mV)\r\n", adc_chars.vref);
break;
default:
Serial.printf("Characterized using Default Vref (%d mV)\r\n", 1100);
}
//to avoid noise, sample the pin several times and average the result
for(int i=1; i<=rounds; i++) {
value += adc1_get_raw(ADC1_CHANNEL_6);
}
value /= (uint32_t)rounds;
//due to the voltage divider (1M+1M) values must be multiplied by 2
//and convert mV to V
return esp_adc_cal_raw_to_voltage(value, &adc_chars)*2.0/1000.0;
}
/******************************************************************************
Description.: send MQTT message
Input Value.: msg selects the message to send
Return Value: true if OK, false if errors occured
******************************************************************************/
bool SendMessage(enum _message msg) {
char buf[256] = {0};
//read RTC
struct timeval tv;
gettimeofday(&tv, NULL);
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str())) {
switch(msg) {
case M_COUNTER:
Serial.printf("Sending counter: %d\r\n", cache.counter);
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
snprintf(buf, sizeof(buf)-1, "%.2f", cache.counter/100.0);
MQTTClient.publish(MQTTRootTopic+"/qubicmeter", buf, false, 2);
break;
case M_STATUS:
Serial.printf("Sending status\r\n");
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
snprintf(buf, sizeof(buf)-1, "%.2f", cache.counter/100.0);
MQTTClient.publish(MQTTRootTopic+"/qubicmeter", buf, false, 2);
MQTTClient.publish(MQTTRootTopic+"/BatteryVoltage", String(cache.BatteryVoltage, 3), false, 2);
snprintf(buf, sizeof(buf)-1, "%ld.%06ld", tv.tv_sec, tv.tv_usec);
MQTTClient.publish(MQTTRootTopic+"/BatteryRuntime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.NumberOfRestarts);
MQTTClient.publish(MQTTRootTopic+"/Restarts", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.ActiveTime);
MQTTClient.publish(MQTTRootTopic+"/ActiveTime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%ld", WiFi.RSSI());
MQTTClient.publish(MQTTRootTopic+"/RSSI", buf, false, 2);
break;
default:
Serial.printf("unkown message, not sending anything\r\n");
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: get the counter from MQTT, if it is retained we use it as start
Input Value.: timeout in ms
Return Value: true if OK, false if errors occured
******************************************************************************/
bool GetCounter(unsigned int timeout) {
char buf[256] = {0};
bool gotCounter = false;
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
//callback as lambda-function, capture gotCounter by reference to signal when done
MQTTClient.onMessage((MQTTClientCallbackSimpleFunction)([&gotCounter](String &topic, String &payload) -> void {
Serial.println("Received MQTT message: " + topic + " - " + payload);
if ( topic == MQTTRootTopic+"/counter") {
cache.counter = strtoull(payload.c_str(), NULL, 10);
gotCounter = true;
}
return;
}));
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str()) ) {
MQTTClient.subscribe(MQTTRootTopic+"/counter");
for (int i=0; i<=timeout; i++) {
MQTTClient.loop();
if(gotCounter) {
Serial.println("received counter, will use it");
break;
}
usleep(1000);
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: since this is a battery sensor, everything happens in setup
and when the tonguing' is done the device enters deep-sleep
Input Value.: -
Return Value: -
******************************************************************************/
void setup() {
//visual feedback when we are active, turn on onboard LED
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
cache.NumberOfRestarts++;
Serial.begin(115200);
Serial.print("===================================================\r\n");
Serial.printf("FireBeetle active\r\n" \
" Compiled at: " __DATE__ " - " __TIME__ "\r\n" \
" ESP-IDF: %s\r\n", esp_get_idf_version());
//read battery voltage
cache.BatteryVoltage = readBattery();
Serial.printf("Voltage: %4.3f V\r\n", cache.BatteryVoltage);
//a reset is required to wakeup again from below CRITICALLY_LOW_BATTERY_VOLTAGE
//this is to prevent damaging the empty battery by saving as much power as possible
if (cache.BatteryVoltage < CRITICALLY_LOW_BATTERY_VOLTAGE) {
Serial.println("Battery critically low, hibernating...");
//switch off everything that might consume power
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_CPU, ESP_PD_OPTION_OFF);
//disable all wakeup sources
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//if battery is below LOW_BATTERY_VOLTAGE but still above CRITICALLY_LOW_BATTERY_VOLTAGE,
//stop doing the regular work
//when put on charge the device will wakeup after a while and recognize voltage is OK
//this way the battery can run low, put still wakeup without physical interaction
if (cache.BatteryVoltage < LOW_BATTERY_VOLTAGE) {
Serial.println("Battery low, deep sleeping...");
//sleep ~60 minutes if battery is CRITICALLY_LOW_BATTERY_VOLTAGE to VERY_LOW_BATTERY_VOLTAGE
//sleep ~10 minutes if battery is VERY_LOW_BATTERY_VOLTAGE to LOW_BATTERY_VOLTAGE
uint64_t sleeptime = (cache.BatteryVoltage >= VERY_LOW_BATTERY_VOLTAGE) ? \
10*60*1000000ULL : 60*60*1000000ULL;
esp_sleep_enable_timer_wakeup(sleeptime);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//read GPIO level
pinMode(REED_GPIO, INPUT_PULLUP);
int level = digitalRead(REED_GPIO);
Serial.printf("Reed-switch is: %s\r\n", (level)?"NOMAGNET":"MAGNET");
//check if a reset/power-on occured
if (esp_reset_reason() == ESP_RST_POWERON) {
Serial.printf("ESP was just switched ON\r\n");
cache.state = S_STARTUP;
cache.ActiveTime = 0;
cache.NumberOfRestarts = 0;
cache.counter = 0;
//set RTC to 0
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
//default is to have WiFi off
if (WiFi.getMode() != WIFI_OFF) {
Serial.printf("WiFi wasn't off!\r\n");
WiFi.persistent(true);
WiFi.mode(WIFI_OFF);
}
//try to connect to WiFi
WiFiUP(false);
//retrieve the previous gasmeter-counter, will return quickly if it is retained
GetCounter(10*1000);
WiFi.disconnect(true, true);
//transition to new state
cache.state = (level == HIGH) ? S_DEBOUNCE_HIGH : S_DEBOUNCE_LOW;
Serial.printf("transition to state nr.: %d\r\n", cache.state);
//wake by timer after debounce time is up
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
}
// check if ESP returns from deepsleep
if (esp_reset_reason() == ESP_RST_DEEPSLEEP) {
switch(esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.printf("ESP woke up due to timer\r\n");
switch(cache.state) {
case S_DEBOUNCE_LOW:
case S_DEBOUNCE_HIGH:
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
cache.counter++;
WiFiUP(true);
(cache.counter % 10 == 0) ? SendMessage(M_STATUS) : SendMessage(M_COUNTER);
WiFi.disconnect(true, true);
}
Serial.printf("transition to state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
case S_IDLE:
WiFiUP(true);
SendMessage(M_STATUS);
WiFi.disconnect(true, true);
Serial.printf("remaining in state S_IDLE\r\n");
cache.state = S_IDLE;
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, (level == LOW)?1:0);
break;
default:
Serial.printf("ESP woke up by timer in an unknown state\r\n");
}
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.printf("ESP woke up by EXT0\r\n");
if (level == HIGH) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("transition to state S_DEBOUNCE_HIGH\r\n");
}
else {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("transition to state S_DEBOUNCE_LOW\r\n");
}
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
break;
default:
Serial.printf("ESP woke up due to an unknown reason\r\n");
}
}
Serial.printf("counter: %llu\r\n", cache.counter);
Serial.printf("=== entering deepsleep after %d ms ===\r\n", millis());
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
}
void loop() {
Serial.println("This should never get printed");
}
I'm impressed. I will connect it to my gas meter soon and run some tests 🙂 is it possible to send mqtt message with current gas meter value so it is possible to sync current state?
Yes, to start from a certain value:
mosquitto_pub -t "Keller/Gaszaehler/counter" -h server.lan -u "username" -P "password" -m "12345" -r
Superb. Thank you very much for this!
Just one more thing. If esp is closed in a box or similar update of current value could be problematic... You need access to dismantle battery for update. Maybe a separate message could be sent with update value and after update it could be set to 0 (and zero would be ignored by software as update value). I hope you get my idea
Regarding real life usage of gas. In my case it is ~ 1000 m3 a year. It can between 800 m3 and 1200 m3 depending on conditions. So to calculate it per impulse ~ 10.000 impulses a year. Not sure how to count estimated life time. I will be using 2 or maybe even 1 pcs of 18650 which has total capacity 3450 mAh. I've seen your charts and with that kind of usage it looks very optimistic.
Ignoring the wakeups of ~60ms for maintaining the counter and just focusing on the transmissions, a 2000 mAh battery should be good for ~70.000 transmissions. Over the thumb I would half the value and would expect it to last for ~35.000 impulses:
A rule of thumb: Put in as much batteries as fits and what is affordable, it is never too much :)
To update the counter from the broker: How about inserting a power switch into the battery line? That way the ESP32 can easily be power-cycled and the wiring for the battery is needed anyway. It will consume zero energy in addition and is quite intuitive and easy to understand.
I totally understand 🙂 but my gas meter is 30m from my house so in case I want to adjust counter I will have to walk so far 😆 i also thought about switch
Ok, the function GetCounter(10*1000)
gets the counter from the broker. You could make it look for the value before each transmission:
...
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
WiFiUP(true);
GetCounter(10*1000);
cache.counter++;
(cache.counter % 10 == 0) ? SendMessage(M_STATUS) : SendMessage(M_COUNTER);
WiFi.disconnect(true, true);
}
...
I would try to get along without reading the value, as it extends the active-time significantly. In your case it might be needed for convenience.
Two more thoughts:
Never heard of these batteries. Are there any compact sizes? I have 2x18650 Sanyo ga.
Test setup running 🙂🙂🙂
I would say, looks good. Mine is now also in place and happily counting and logging. We will see how well the battery lasts. I mounted a 2000 mAh LiPo-Pouch just like for the PIR sensor.
I leave it for 2 weeks like this. Voltage is 4.1
Btw do you know a good way to add external antenna to Firebeetle?
Cut the "low-power" pad to the on-board RGB LED, otherwise the Firebeetle idle current is not low.
I don't get it... What has to be done? I can't find any guides online..
https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654 --> Low Power Pad: This pad is specially designed for low power consumption. It is connected by default. You can cut off the thin wire in the middle with a knife to disconnect it. After disconnection, the static power consumption can be reduced by 500 μA. The power consumption can be reduced to 13 μA after controlling the maincontroller enter the sleep mode through the program. Note: when the pad is disconnected, you can only drive RGB LED light via the USB Power supply.
Thank you.
Good news: It counts and runs from battery, Bad news: it misses a few revolutions. I am afraid there is still something to be done to read the reed-switch properly.
Mine is in sync so far
In my case it was missing some counts when the gas-heater was ramping up to the maximum power. For such cases I had to reduce the BOUNCE_TIME to 500msec. This might be a value that needs to be adjusted to each reed-setup and gas-heater. If your setup is working there is no need to change it, in my case it was necessary.
Here is the sketch in its current revision. I also implemented the reaction to all states I could imagine and coded it very verbose to not miss a state:
/*******************************************************************************
# #
# Using the Firebeetle 2 ESP32-E as battery powered gasmeter-reader #
# Project: https://github.com/Torxgewinde/Firebeetle-2-ESP32-E #
# #
# Firebeetle documentation at: #
# https://wiki.dfrobot.com/FireBeetle_Board_ESP32_E_SKU_DFR0654 #
# #
# Copyright (C) 2022 Tom Stöveken #
# #
# 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; version 2 of the License. #
# #
# 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, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
********************************************************************************/
#include <WiFi.h>
#include <MQTT.h>
#include <stdlib.h>
#include "esp_adc_cal.h"
#include "driver/rtc_io.h"
#define ESSID "My Wifi SSID"
#define PSK "My Wifi Password"
#define LOW_BATTERY_VOLTAGE 3.20
#define VERY_LOW_BATTERY_VOLTAGE 3.10
#define CRITICALLY_LOW_BATTERY_VOLTAGE 3.00
//char *MQTTServer = "server.lan";
IPAddress MQTTServer = IPAddress(192,168,1,1);
uint16_t MQTTPort = 1883;
String MQTTUsername = "username";
String MQTTPassword = "password";
String MQTTDeviceName = "Gaszaehler";
String MQTTRootTopic = "Keller/Gaszaehler";
enum _state {
S_STARTUP = 0,
S_DEBOUNCE_LOW,
S_DEBOUNCE_HIGH,
S_LOW,
S_HIGH
};
enum _message {
M_COUNTER = 0,
M_STATUS
};
RTC_NOINIT_ATTR struct {
uint8_t bssid[6];
uint8_t channel;
float BatteryVoltage; //battery voltage in V
uint64_t NumberOfRestarts; //number of restarts
uint64_t ActiveTime; //time being active in ms
enum _state state; //keep track of current state
uint64_t counter; //gasmeter-counter
} cache;
//REED contact is connected to GPIO4 (Pin: D12)
#define REED_GPIO 4
#define REED_DEEPSLEEP_PIN GPIO_NUM_4
//Time the reed-switch must remain in a certain state before level considered valid
//#define DEBOUNCE_TIME 1*1000000ULL // 1000 msec
#define DEBOUNCE_TIME 500*1000ULL //500 msec
//periodically wakeup and report battery status
#define LONG_TIME 2*60*60*1000000ULL // 2h
/******************************************************************************
Description.: bring the WiFi up
Input Value.: When "tryCachedValuesFirst" is true the function tries to use
cached values before attempting a scan + association
Return Value: true if WiFi is up, false if it timed out
******************************************************************************/
bool WiFiUP(bool tryCachedValuesFirst) {
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
if(tryCachedValuesFirst && cache.channel > 0) {
Serial.printf("Cached values as follows:\r\n");
Serial.printf(" Channel....: %d\r\n", cache.channel);
Serial.printf(" BSSID......: %x:%x:%x:%x:%x:%x\r\n", cache.bssid[0], \
cache.bssid[1], \
cache.bssid[2], \
cache.bssid[3], \
cache.bssid[4], \
cache.bssid[5]);
WiFi.begin(ESSID, PSK, cache.channel, cache.bssid);
for (unsigned long i=millis(); millis()-i < 10000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected with cached values (%lu)\r\n", millis()-i);
return true;
}
}
}
cache.channel = 0;
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = 0;
// try it with the slower process
WiFi.begin(ESSID, PSK);
for (unsigned long i=millis(); millis()-i < 60000;) {
delay(10);
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi connected (%lu)\r\n", millis()-i);
uint8_t *bssid = WiFi.BSSID();
for (uint32_t i = 0; i < sizeof(cache.bssid); i++)
cache.bssid[i] = bssid[i];
cache.channel = WiFi.channel();
return true;
}
}
Serial.printf("WiFi NOT connected\r\n");
return false;
}
/******************************************************************************
Description.: reads the battery voltage through the voltage divider at GPIO34
if the ESP32-E has calibration eFused those will be used.
In comparison with a regular voltmeter the values of ESP32 and
multimeter differ only about 0.05V
Input Value.: -
Return Value: battery voltage in volts
******************************************************************************/
float readBattery() {
uint32_t value = 0;
int rounds = 11;
esp_adc_cal_characteristics_t adc_chars;
//battery voltage divided by 2 can be measured at GPIO34, which equals ADC1_CHANNEL6
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
switch(esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars)) {
case ESP_ADC_CAL_VAL_EFUSE_TP:
Serial.println("Characterized using Two Point Value");
break;
case ESP_ADC_CAL_VAL_EFUSE_VREF:
Serial.printf("Characterized using eFuse Vref (%d mV)\r\n", adc_chars.vref);
break;
default:
Serial.printf("Characterized using Default Vref (%d mV)\r\n", 1100);
}
//to avoid noise, sample the pin several times and average the result
for(int i=1; i<=rounds; i++) {
value += adc1_get_raw(ADC1_CHANNEL_6);
}
value /= (uint32_t)rounds;
//due to the voltage divider (1M+1M) values must be multiplied by 2
//and convert mV to V
return esp_adc_cal_raw_to_voltage(value, &adc_chars)*2.0/1000.0;
}
/******************************************************************************
Description.: send MQTT message
Input Value.: msg selects the message to send
Return Value: true if OK, false if errors occured
******************************************************************************/
bool SendMessage(enum _message msg) {
char buf[256] = {0};
//read RTC
struct timeval tv;
gettimeofday(&tv, NULL);
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str())) {
switch(msg) {
case M_COUNTER:
Serial.printf("Sending counter: %d\r\n", cache.counter);
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
snprintf(buf, sizeof(buf)-1, "%.2f", cache.counter/100.0);
MQTTClient.publish(MQTTRootTopic+"/qubicmeter", buf, false, 2);
break;
case M_STATUS:
Serial.printf("Sending status\r\n");
MQTTClient.publish(MQTTRootTopic+"/counter", String(cache.counter), true, 2);
snprintf(buf, sizeof(buf)-1, "%.2f", cache.counter/100.0);
MQTTClient.publish(MQTTRootTopic+"/qubicmeter", buf, false, 2);
MQTTClient.publish(MQTTRootTopic+"/BatteryVoltage", String(cache.BatteryVoltage, 3), false, 2);
snprintf(buf, sizeof(buf)-1, "%ld.%06ld", tv.tv_sec, tv.tv_usec);
MQTTClient.publish(MQTTRootTopic+"/BatteryRuntime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.NumberOfRestarts);
MQTTClient.publish(MQTTRootTopic+"/Restarts", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%llu", cache.ActiveTime);
MQTTClient.publish(MQTTRootTopic+"/ActiveTime", buf, false, 2);
snprintf(buf, sizeof(buf)-1, "%ld", WiFi.RSSI());
MQTTClient.publish(MQTTRootTopic+"/RSSI", buf, false, 2);
break;
default:
Serial.printf("unkown message, not sending anything\r\n");
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: get the counter from MQTT, if it is retained we use it as start
Input Value.: timeout in ms
Return Value: true if OK, false if errors occured
******************************************************************************/
bool GetCounter(unsigned int timeout) {
char buf[256] = {0};
bool gotCounter = false;
//establish connection to MQTT server
WiFiClient net;
MQTTClient MQTTClient(256);
MQTTClient.begin(MQTTServer, MQTTPort, net);
MQTTClient.setOptions(30, true, 5000);
//callback as lambda-function, capture gotCounter by reference to signal when done
MQTTClient.onMessage((MQTTClientCallbackSimpleFunction)([&gotCounter](String &topic, String &payload) -> void {
Serial.println("Received MQTT message: " + topic + " - " + payload);
if ( topic == MQTTRootTopic+"/counter") {
cache.counter = strtoull(payload.c_str(), NULL, 10);
gotCounter = true;
}
return;
}));
if( MQTTClient.connect(MQTTDeviceName.c_str(), MQTTUsername.c_str(), MQTTPassword.c_str()) ) {
MQTTClient.subscribe(MQTTRootTopic+"/counter");
for (int i=0; i<=timeout; i++) {
MQTTClient.loop();
if(gotCounter) {
Serial.println("received counter, will use it");
break;
}
usleep(1000);
}
MQTTClient.disconnect();
return true;
}
return false;
}
/******************************************************************************
Description.: since this is a battery sensor, everything happens in setup
and when the tonguing' is done the device enters deep-sleep
Input Value.: -
Return Value: -
******************************************************************************/
void setup() {
//visual feedback when we are active, turn on onboard LED
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
cache.NumberOfRestarts++;
Serial.begin(115200);
Serial.print("===================================================\r\n");
Serial.printf("FireBeetle active\r\n" \
" Compiled at: " __DATE__ " - " __TIME__ "\r\n" \
" ESP-IDF: %s\r\n", esp_get_idf_version());
//read battery voltage
cache.BatteryVoltage = readBattery();
Serial.printf("Voltage: %4.3f V\r\n", cache.BatteryVoltage);
//a reset is required to wakeup again from below CRITICALLY_LOW_BATTERY_VOLTAGE
//this is to prevent damaging the empty battery by saving as much power as possible
if (cache.BatteryVoltage < CRITICALLY_LOW_BATTERY_VOLTAGE) {
Serial.println("Battery critically low, hibernating...");
//switch off everything that might consume power
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
//esp_sleep_pd_config(ESP_PD_DOMAIN_CPU, ESP_PD_OPTION_OFF);
//disable all wakeup sources
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//if battery is below LOW_BATTERY_VOLTAGE but still above CRITICALLY_LOW_BATTERY_VOLTAGE,
//stop doing the regular work
//when put on charge the device will wakeup after a while and recognize voltage is OK
//this way the battery can run low, put still wakeup without physical interaction
if (cache.BatteryVoltage < LOW_BATTERY_VOLTAGE) {
Serial.println("Battery low, deep sleeping...");
//sleep ~60 minutes if battery is CRITICALLY_LOW_BATTERY_VOLTAGE to VERY_LOW_BATTERY_VOLTAGE
//sleep ~10 minutes if battery is VERY_LOW_BATTERY_VOLTAGE to LOW_BATTERY_VOLTAGE
uint64_t sleeptime = (cache.BatteryVoltage >= VERY_LOW_BATTERY_VOLTAGE) ? \
10*60*1000000ULL : 60*60*1000000ULL;
esp_sleep_enable_timer_wakeup(sleeptime);
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
return;
}
//configure GPIO of reed-switch
pinMode(REED_GPIO, INPUT_PULLUP);
rtc_gpio_pullup_en(REED_DEEPSLEEP_PIN);
rtc_gpio_pulldown_dis(REED_DEEPSLEEP_PIN);
//read level of reed-switch
int level = digitalRead(REED_GPIO);
Serial.printf("Reed-switch: %s\r\n", (level)?"NOMAGNET (=HIGH)":"MAGNET (=LOW)");
//check if a reset/power-on occured
if (esp_reset_reason() == ESP_RST_POWERON) {
Serial.printf("ESP was just switched ON\r\n");
cache.state = S_STARTUP;
cache.ActiveTime = 0;
cache.NumberOfRestarts = 0;
cache.counter = 0;
//set RTC to 0
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
//default is to have WiFi off
if (WiFi.getMode() != WIFI_OFF) {
Serial.printf("WiFi wasn't off!\r\n");
WiFi.persistent(true);
WiFi.mode(WIFI_OFF);
}
//try to connect to WiFi
WiFiUP(false);
//retrieve the previous gasmeter-counter, will return quickly if it is retained
GetCounter(10*1000);
WiFi.disconnect(true, true);
//transition to new state
if(level == HIGH) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("RESET: S_STARTUP -> S_DEBOUNCE_HIGH\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
} else {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("RESET: S_STARTUP -> S_DEBOUNCE_LOW\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
}
}
// check if ESP returns from deepsleep
if (esp_reset_reason() == ESP_RST_DEEPSLEEP) {
switch(esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.printf("ESP woke up due to timer\r\n");
//This state should not occur, no debounce state
if(level == HIGH && cache.state == S_LOW) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("TIMER: S_LOW -> S_DEBOUNCE_HIGH\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//can occur if LONG_TIME passed and level remains low
if(level == LOW && cache.state == S_LOW) {
cache.state = S_LOW;
Serial.printf("TIMER: S_LOW -> S_LOW\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
WiFiUP(true);
SendMessage(M_STATUS);
WiFi.disconnect(true, true);
break;
}
//can occur if LONG_TIME passed and level remains high
if(level == HIGH && cache.state == S_HIGH) {
cache.state = S_HIGH;
Serial.printf("TIMER: S_HIGH -> S_HIGH\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
WiFiUP(true);
SendMessage(M_STATUS);
WiFi.disconnect(true, true);
break;
}
//this state should not occur, no debounce state
if(level == LOW && cache.state == S_HIGH) {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("TIMER: S_HIGH -> S_DEBOUNCE_LOW\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
//level was HIGH for the whole DEBOUNCE_TIME and still is
if(level == HIGH && cache.state == S_DEBOUNCE_HIGH) {
cache.state = S_HIGH;
Serial.printf("TIMER: S_DEBOUNCE_HIGH -> S_HIGH\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//timer is up, but level changed without triggering EXT0, strange but deal with it
if(level == LOW && cache.state == S_DEBOUNCE_HIGH) {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("TIMER: S_DEBOUNCE_HIGH -> S_DEBOUNCE_LOW\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
//timer is up, but level changed without triggering EXT0, strange but deal with it
if(level == HIGH && cache.state == S_DEBOUNCE_LOW) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("TIMER: S_DEBOUNCE_LOW -> S_DEBOUNCE_HIGH\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//level was LOW for the whole debounce time and still is
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
cache.state = S_LOW;
Serial.printf("TIMER: S_DEBOUNCE_LOW -> S_LOW\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
WiFiUP(true);
cache.counter++;
(cache.counter % 10 == 0) ? SendMessage(M_STATUS) : SendMessage(M_COUNTER);
WiFi.disconnect(true, true);
break;
}
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.printf("ESP woke up by EXT0\r\n");
//level just changed from LOW to HIGH, start debounce time
if(level == HIGH && cache.state == S_LOW) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("EXT0: S_LOW -> S_DEBOUNCE_HIGH\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//Level was LOW, still is, no reason to change state
if(level == LOW && cache.state == S_LOW) {
cache.state = S_LOW;
Serial.printf("EXT0: S_LOW -> S_LOW\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
//level was HIGH, still is, no reason to change state
if(level == HIGH && cache.state == S_HIGH) {
cache.state = S_HIGH;
Serial.printf("EXT0: S_HIGH -> S_HIGH\r\n");
esp_sleep_enable_timer_wakeup(LONG_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//level was HIGH, just changed to LOW, start to debounce, trigger EXT0 if bouncing to HIGH
if(level == LOW && cache.state == S_HIGH) {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("EXT0: S_HIGH -> S_DEBOUNCE_LOW\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
//level was HIGH, still is, however something triggered EXT0, restart DEBOUNCE_TIME
if(level == HIGH && cache.state == S_DEBOUNCE_HIGH) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("EXT0: S_DEBOUNCE_HIGH -> S_DEBOUNCE_HIGH, but restart DEBOUNCE_TIME\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//level was HIGH, just changed to LOW, might be just bouncing
if(level == LOW && cache.state == S_DEBOUNCE_HIGH) {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("EXT0: S_DEBOUNCE_HIGH -> S_DEBOUNCE_LOW\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
//level was LOW, now it is HIGH, might just be bouncing
if(level == HIGH && cache.state == S_DEBOUNCE_LOW) {
cache.state = S_DEBOUNCE_HIGH;
Serial.printf("EXT0: S_DEBOUNCE_LOW -> S_DEBOUNCE_HIGH\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, LOW);
break;
}
//level was LOW, still is, however something triggered EXT0, restart DEBOUNCE_TIME
if(level == LOW && cache.state == S_DEBOUNCE_LOW) {
cache.state = S_DEBOUNCE_LOW;
Serial.printf("EXT0: S_DEBOUNCE_LOW -> S_DEBOUNCE_LOW, but restart DEBOUNCE_TIME\r\n");
esp_sleep_enable_timer_wakeup(DEBOUNCE_TIME);
esp_sleep_enable_ext0_wakeup(REED_DEEPSLEEP_PIN, HIGH);
break;
}
break;
default:
Serial.printf("ESP woke up due to an unknown reason\r\n");
}
}
Serial.printf("counter: %llu\r\n", cache.counter);
Serial.printf("=== entering deepsleep after %d ms ===\r\n", millis());
cache.ActiveTime += millis();
digitalWrite(2, LOW);
esp_deep_sleep_start();
Serial.println("This should never get printed");
}
void loop() {
Serial.println("This should never get printed");
}
One question. What is the unit of measurement of time on batteries and active time?
The battery unit is in Volt. Active Time is the time the ESP32 is in active mode in milliseconds. BatteryRuntime is the time since power-up/reset in seconds (as float, "seconds.subseconds" ). BatteryRuntime does not have a very accurate clock source, so it is just a course time.
RTC_NOINIT_ATTR struct {
uint8_t bssid[6];
uint8_t channel;
float BatteryVoltage; //battery voltage in V
uint64_t NumberOfRestarts; //number of restarts
uint64_t ActiveTime; //time being active in ms
enum _state state; //keep track of current state
uint64_t counter; //gasmeter-counter
} cache;
Here are some stats for period from 09.01-18.01. I didn't cut the low power connection yet.
Ok, about the Low-Power-Pad, there is a lot of power that is drawn by the RGB-LED even if it is not emitting light. For my particular reed-switch I adjusted the bounce-time down to 150 ms, but if 1000 ms works for your setup I would just keep it that way.
@imabot2 has an informative and detailed article in his blog about the low-power-pad: https://lucidar.me/en/esp32/power-consumption-of-esp32-firebeetle-dfr0654/
Which gpio pins are used to connect pir sensor? Is it possible to use reed sensor instead? I would lile to use this project for gas meter.