Battery powered weather station with ESP8266 and BME280
I used to use web services such as OpenWeatherMap to monitor outside temperature in openHAB. I found out that sometimes they’re not very accurate and naturally they fail when the internet is down. So I decided to measure the temperature myself. After some searching, I decided to settle with battery powered ESP8266 wireless solution. The advantages are the following:
- no wires;
- low cost;
- easy programming via familiar Arduino IDE;
- long battery operation with deep sleep mode.
I’ve chosen ESP-01 module mostly due to its popularity and low cost. However, to reduce power consumption it requires two hardware modifications: removing power LED (which is always on otherwise) and soldering pin #8 of ESP8266 chip to RST pin of the module. The latter is especially tricky—the chip pins are very tiny—so if you’re not comfortable you should probably choose ESP-03 module instead.
To measure temperature I use BME280 environmental sensors which also provide humidity and pressure readings. I prefer them over DHT-22 which are less accurate, very slow and generally less reliable. You can get BME280s in neat ready to use modules.
To program ESP-01 I used UART module I already had which supports 3.3V, but I’d highly recommend a specialized one. To enter programming mode, you need to connect GPIO0 to GND and CH_PD pin to VCC, and this module has a special switch for that. This makes debugging via serial connection much easier.
I need the setup to operate in cold temperatures (down to -20°C), so I chose 3×AA Ni-MH rechargeable batteries which are ubiquitous and behave better in such environment than Li-ion, for example. Three batteries connected in series give more than 4V fully charged which is above 3–3.6V datasheet range but seem to work nevertheless.
To summarize, you’ll need the following:
- ESP-01 (or ESP-03, see above) module;
- BME280 sensor module;
- 3.3V USB UART;
- 3×AA Ni-MH batteries with holder;
- soldering iron, wires (breadboard, jump wires, etc.—optional);
- PC with USB port and Arduino IDE.
On the software side, I used existing openHAB installation. To communicate with ESP-01 it needs running MQTT broker and corresponding addon. Fortunately, broker installation is simple:
wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key
cd /etc/apt/sources.list.d/
sudo wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list
sudo apt-get install mosquitto
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
openHAB addon can (probably) be installed via Paper UI, but I used legacy config:
sudo nano /etc/openhab2/services/addons.cfg
binding = ..., mqtt1
sudo rm /var/lib/openhab2/config/org/openhab/mqtt.config
sudo nano /etc/openhab2/services/mqtt.cfg
mosquitto.url=tcp://localhost:1883
I found that the best way to debug Mosquitto is to run it in terminal:
sudo systemctl stop mosquitto
sudo mosquitto -p 1883 -v
I did the wiring and debugging in following steps:
- Connected ESP-01 to UART and check if it works. I used WiFiScan example for that.
- I started with a great sketch by Marcel Akkerman but replaced BME280 code with dummy random values. Don’t forget to create
config.h
file with your parameters! - When I got that running, I enabled BME280 code and connected the sensor. I was pleasantly surprised when it worked and started sending data.
- Then I soldered deep sleep pins and brutally destroyed power LED on ESP-01 and added
ESP.deepSleep(10*1000*1000)
call to the sketch which sends it to sleep for just 10 seconds. For debugging purposes, I disabled BME280 readout again and was removing GND wire from CH_PD pin of ESP as soon as the sketch was 100% uploaded. This way I could see the serial output. - When I was happy with results, I increased the interval to 900 seconds, disabled serial output and enabled BME280 again.
As the last step, I used RX (GPIO3) pin on ESP-01 to power BME280 sensor. That prevents it from draining battery when the deep sleep is active.
Here’s the final wiring scheme:
And here’s the sketch for ESP-01:
// ESP-01 with BME-280 via MQTT with deep sleep
// Based on https://github.com/akkerman/espthp
#include <string>
#include <esp8266wifi.h>
#include <pubsubclient.h>
#include <adafruit_sensor.h>
#include <adafruit_bme280.h>
#include <ticker.h>
#include "config.h"
#define STATUS_DISCONNECTED "disconnected"
#define STATUS_ONLINE "online"
#define DEEP_SLEEP_S 900 // 900 s = 15 min.
#define BME280ON // Comment to disable BME280 sensor readout.
//#define DEBUG // Comment to disable debug serial output.
#ifdef DEBUG
#define DPRINT(...) Serial.print(__VA_ARGS__)
#define DPRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define DPRINT(...)
#define DPRINTLN(...)
#endif
ADC_MODE(ADC_VCC);
unsigned long previousMillis = 0;
const long interval = 10 * 60 * 1000;
const String chipId = String(ESP.getChipId());
const String hostName = "ESP-01_" + chipId;
const String baseTopic = "raw/" + chipId + "/";
const String tempTopic = baseTopic + "temperature";
const String humiTopic = baseTopic + "humidity";
const String presTopic = baseTopic + "pressure";
const String willTopic = baseTopic + "status";
const String vccTopic = baseTopic + "vcc";
const String ipTopic = baseTopic + "ip";
IPAddress ip;
WiFiClient WiFiClient;
PubSubClient client(WiFiClient);
#ifdef BME280ON
Adafruit_BME280 bme; // I2C
#endif
Ticker ticker;
char temperature[6];
char humidity[6];
char pressure[7];
char vcc[10];
void setup() {
#ifdef DEBUG
Serial.begin(9600);
#else
pinMode(3, OUTPUT); // Power up BME280 when powered via RX (GPIO3).
digitalWrite(3, HIGH);
#endif
delay(10);
#ifdef BME280ON
Wire.begin(0, 2);
Wire.setClock(100000);
if (!bme.begin(0x76)) {
DPRINTLN("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
#endif
DPRINTLN();
DPRINT("Connecting to ");
DPRINTLN(WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.hostname(hostName);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
DPRINT(".");
}
DPRINTLN();
DPRINTLN("WiFi connected");
DPRINT("IP address: ");
ip = WiFi.localIP();
DPRINTLN(ip);
client.setServer(MQTT_IP, MQTT_PORT);
client.setCallback(callback);
ticker.attach(600.0, publish);
bool publishNow = false;
yield();
if (!client.connected()) {
DPRINTLN("Attempting MQTT connection...");
if (client.connect(chipId.c_str(), willTopic.c_str(), 1, true, STATUS_DISCONNECTED)) {
DPRINTLN("MQTT client connected.");
client.publish(willTopic.c_str(), STATUS_ONLINE, true);
client.publish(ipTopic.c_str(), ip.toString().c_str(), true);
client.subscribe("config/publish");
publishNow = true;
} else {
DPRINT("failed, rc=");
DPRINTLN(client.state());
}
}
bmeRead();
vccRead();
#ifndef DEBUG
digitalWrite(3, LOW);
#endif
if (client.connected()) {
client.loop();
if (publishNow) {
publish();
publishNow = false;
}
}
client.disconnect();
delay(500); // Needed to properly close connection.
WiFi.disconnect();
DPRINTLN("Going to deep sleep...");
ESP.deepSleep(DEEP_SLEEP_S * 1000 * 1000);
}
void loop() {
}
void bmeRead() {
#ifdef BME280ON
float t = bme.readTemperature();
float h = bme.readHumidity();
float p = bme.readPressure() / 100.0F;
#else
float t = 20 + random(5);
float h = 50 + random(10);
float p = 1013 + random(10);
#endif
sprintf(temperature, "%.1f", t);
sprintf(humidity, "%.1f", h);
sprintf(pressure, "%.1f", p);
}
void printMeasurements() {
DPRINT("t,h,p: ");
DPRINT(temperature); DPRINT(", ");
DPRINT(humidity); DPRINT(", ");
DPRINT(pressure); DPRINTLN();
DPRINT("vcc: "); DPRINTLN(vcc);
}
void publish() {
printMeasurements();
if (client.connected()) {
DPRINTLN("publishing values");
DPRINTLN(baseTopic);
client.publish(tempTopic.c_str(), temperature);
client.publish(humiTopic.c_str(), humidity);
client.publish(presTopic.c_str(), pressure);
client.publish(vccTopic.c_str(), vcc);
} else {
DPRINTLN("client not connected, not publishing");
}
}
void vccRead() {
float v = ESP.getVcc() / 1000.0;
sprintf(vcc, "%.2f", v);
}
void callback(char* topic, byte* payload, unsigned int length) {
std::string s( reinterpret_cast<char const*>(payload), length );
Serial.print("message arrived: ");
Serial.print(topic);
Serial.print(" - ");
Serial.print(s.c_str());
Serial.println();
if (s == "all" || s == chipId.c_str()) {
publish();
}
}
The configuration file config.h
:
#define WIFI_SSID ""
#define WIFI_PASS ""
#define MQTT_IP "192.168.0.107"
#define MQTT_PORT 1883
If you use password authentication in MQTT, add the credentials to the connect
function in the sketch:
client.connect(chipId.c_str(), "user", "password", willTopic.c_str(), 1, true, STATUS_DISCONNECTED)
openHAB
Unfortunately, MQTT operates with strings and to draw plots and create conditions in openHAB we need numbers. So I created a set of hacky rules to convert values on each change:
mqtt.items:
String SVoltageESP1 "ESP voltage [%s V]" {mqtt="<[mosquitto:raw/4896388/vcc:state:default]"}
String STemperatureESP1 "Outside temperature (ESP1) [%s °C]" {mqtt="<[mosquitto:raw/1234567/temperature:state:default]"}
String SHumidityESP1 "Outside humidity (ESP1) [%s%%]" {mqtt="<[mosquitto:raw/1234567/humidity:state:default]"}
String SPressureESP1 "Pressure (ESP1) [%s hPa]" {mqtt="<[mosquitto:raw/1234567/pressure:state:default]"}
Number VoltageESP1 "Voltage (ESP1) [%.2f V]" (Voltages)
Number TemperatureESP1 "Outside temperature [%.1f °C]" (Temperatures)
Number HumidityESP1 "Outside humidity [%.1f%%]" (Humidities)
Number PressureESP1 "Outside pressure [%.1f hPa]" (Pressures)
mqtt.rules
:
rule "VoltageESP1 String to Number"
when
Item SVoltageESP1 changed
then
VoltageESP1.postUpdate(Float::parseFloat(String::format("%s",SVoltageESP1.state).replace(' ','')))
end
rule "TemperatureESP1 String to Number"
when
Item STemperatureESP1 changed
then
TemperatureESP1.postUpdate(Float::parseFloat(String::format("%s",STemperatureESP1.state).replace(' ','')))
end
rule "HumidityESP1 String to Number"
when
Item SHumidityESP1 changed
then
HumidityESP1.postUpdate(Float::parseFloat(String::format("%s",SHumidityESP1.state).replace(' ','')))
end
rule "PressureESP1 String to Number"
when
Item SPressureESP1 changed
then
PressureESP1.postUpdate(Float::parseFloat(String::format("%s",SPressureESP1.state).replace(' ','')))
end
Home Assistant
To use the data in Home Assistant you should enable MQTT integration and put the following values to the sensor
section of configuration.yaml
:
sensor: - platform: mqtt name: "esp01_temperature" unique_id: "esp01_temperature" unit_of_measurement: "°C" device_class: temperature state_topic: "raw/1234567/temperature" - platform: mqtt name: "esp01_humidity" unique_id: "esp01_humidity" device_class: humidity unit_of_measurement: "%" state_topic: "raw/1234567/humidity" - platform: mqtt name: "esp01_pressure" unique_id: "esp01_pressure" device_class: pressure unit_of_measurement: "hPa" state_topic: "raw/1234567/pressure" - platform: mqtt name: "esp01_voltage" unique_id: "esp01_voltage" device_class: voltage unit_of_measurement: "V" state_topic: "raw/1234567/vcc"
Don’t forget to replace 1234567
with the chip Id of your ESP or hard-code your own baseTopic
in the sketch.
The first test with some old used batteries showed that the setup worked down to at least 2.3V.
UPD Nov’18: So far with a set of few years old 2000 mAh AA NiMH batteries the setup works for 2 months+ in summer and ~1 month in winter.
UPD: I replaced dtostrf
with sprintf
to remove spaces from the published strings, since some software has troubles converting them back to numbers.
UPD: Added Home Assistant config.
UPD: In 2022 Home Assistant introduced new config format for MQTT so the relevant section should now look like this:
mqtt: sensor: - name: "esp01_temperature" unique_id: "esp01_temperature" state_topic: "raw/1234567/temperature" device_class: temperature unit_of_measurement: "°C" - name: "esp01_humidity" unique_id: "esp01_humidity" device_class: humidity unit_of_measurement: "%" state_topic: "raw/1234567/humidity" - name: "esp01_pressure" unique_id: "esp01_pressure" device_class: pressure unit_of_measurement: "hPa" state_topic: "raw/1234567/pressure" - name: "esp01_voltage" unique_id: "esp01_voltage" device_class: voltage unit_of_measurement: "V" state_topic: "raw/1234567/vcc"
UPD: I’ve replaced AA batteries with a 3.2 V 1800 mAh LiFePO4 battery and it works pretty well. The downside is you need a special charger for those, the regular Li-ion won’t do. See the voltage plot below (taken in the temperature range of -14° to +7°C):
Hi,
I try to use your sketch but I use a username an password to connect mqtt. Unfortunately I am not a programmer is there a line where I just can add password and username?
Thank you & regards
Marco
Hi Marco,
I believe to enable auth you should change line 102 from:
if (client.connect(chipId.c_str(), willTopic.c_str(), 1, true, STATUS_DISCONNECTED))
to:
if (client.connect(chipId.c_str(), “username”, “password”, willTopic.c_str(), 1, true, STATUS_DISCONNECTED))
Source: https://pubsubclient.knolleary.net/api.html#connect4
Good luck!
PS: Make sure the quotes are regular, WordPress changed them to curly ones in my comment.
Thank you! Connection to mqtt is working now 🙂
So I’m completely new to this!
What should I do when I get:
In function ‘void setup()’:
sketch_aug14b:80: error: ‘WIFI_SSID’ was not declared in this scope
WiFi.begin(WIFI_SSID, WIFI_PASS);
^
sketch_aug14b:80: error: ‘WIFI_PASS’ was not declared in this scope
WiFi.begin(WIFI_SSID, WIFI_PASS);
^
sketch_aug14b:93: error: ‘MQTT_IP’ was not declared in this scope
client.setServer(MQTT_IP, MQTT_PORT);
^
sketch_aug14b:93: error: ‘MQTT_PORT’ was not declared in this scope
client.setServer(MQTT_IP, MQTT_PORT);
^
exit status 1
‘WIFI_SSID’ was not declared in this scope
Make sure you have config.h file in the same folder with those parameters (see example here: https://github.com/akkerman/espthp#pre-upload). I’ll add it to this post too.
“I’ll update this post with long term testing results.”
This.
Fair point! So far with a set of few years old 2000 mAh AA NiMH batteries the setup works for 2 months+ in summer and ~1 month in winter.
hi,
Great project.
Can you share some sketch with an web interface to change the network settings ?
It is not practical to connect the device to pc every time you change your SSID or password.
Thanks!
I guess you’re referring to something like this: https://github.com/tzapu/WiFiManager. Looks handy and easy to use. Never tried it myself though, haven’t changed my Wifi settings in years. 🙂
Thank you for this excellent article! Best tutorial I’ve found, including all youtube videos.
Maybe you can add a photo of your final setup?
Haha, no way, sorry. 🙂 It’s just too ugly to show — basically it’s the scheme shown above stuffed in a plastic box with a hole for the sensor to react faster.
Is there way to monitor battery levels somehow? Like putting alarm or something when 10% or less is battery left.
I’ve just added ‘Text item=VoltageESP1’ to my openHAB sitemap with a dynamic icon turning “empty” at 2V (see https://www.openhab.org/docs/configuration/items.html#dynamic-icons). You can get the icons here: https://glsk.net/wp-content/uploads/2019/02/esp_battery.zip.
If you want push messages, maybe the easiest way is to use openHAB Cloud (https://www.openhab.org/addons/integrations/openhabcloud/). Among other things, they offer push notifications to mobile app. It never worked for me though, since I don’t have Google Play Services on my phone.
Hi,
thanks for your great project, what I’m exactly looking for.
But I have a problem: I can read only the values from the esp out of the MQTT server, but not from the BME. These values shows always “nan” (not a number).
When I connect the BME directly to VCC everything is fine, but not when powering it with GPIO3.
I am using 3xEneloop (~3,9V). GPIO3 switches every 10 seconds between 3.7V and 3.5V, but never goes down to 0V as I expected.
Do you have any idea?
Best regards
Ingo
Now that I think of it, maybe powering off BME280 doesn’t make too much sense in the first place. The sensor itself consumes just 0.1 uA in sleep mode, that’s less than 0.1 mAh per month. I never did any long term tests myself, maybe you want to try?
Anyway, a stupid question: are you sure ‘digitalWrite(3, LOW)’ is actually being called before going to sleep? In my sketch it’s executed only if DEBUG is not defined.
Hi glsk, yes, I do call ‘digitalWrite(3, LOW)’ because DEBUG is not defined. That’s strange, isn’t it?
Regarding the powering of BME280, 0.1 uA is not very much. I think I have to use it in MODE_FORCED, so it goes to sleep mode after a read. I have no experience with it, but I will try and tell you about it.
Ingo
Hi!
I’m trying to achieve something similar to the above but using a ds18b20 temperature sensor instead, and I’m facing some issues powering the esp-01 :(. For some reason, it doesn’t power on 3 regular AA batteries (Duracell, not rechargeable) connected in series with a pull down regulator (AMS1117-3.3 3.3V) and 2 electrolytic capacitors (100uf, 10uf). Still, when I connect the entire setup on RPi it powers on perfectly fine. I even tried with 4 AA batteries and it powered on, but I don’t think this is the way to go since replacing 4 batteries is not cost-effective. The output voltage on those 3 AA is about 4.6V and the pull down regulator reduces it to a steady 3.24V. Connecting the ESP-01, reduces the voltage even more to about 2V.
Am I missing a point here, should I have gone with rechargeable batteries instead?
Thanks & really appreciate this tutorial! :thumbsup:
That’s odd. My ESP-01 works just fine in ~2.5-4.2V range. According to the datasheet the maximum dropout voltage of AMS1117 is 1.3V, may this be the reason of your troubles (4.6 – 1.3 = 3.3)?
BTW, are you sure this regulator won’t drain the battery too quickly? Since DS18B20 can go up to 5.5V I’d try powering your setup with 3 AAs directly. But better have a spare ESP-01 just in case. 😉
Hi,
I try to use your sketch but i get an error. In the last function. See below.
main:163:45: error: expected ‘>’ before ‘*=’ token
std::string s( reinterpret_cast(payload), length );
^
main:163:45: error: expected ‘(‘ before ‘*=’ token
main:163:45: error: expected primary-expression before ‘*=’ token
main:163:58: error: comparison between distinct pointer types ‘const char*’ and ‘byte* {aka unsigned char*}’ lacks a cast [-fpermissive]
std::string s( reinterpret_cast(payload), length );
^
I can’t really understand the problem.
Best regards
Dirk
Thank you very much for noticing this. This part of code got messed up during my WordPress upgrade. I’ve fixed this part, please check if everything else is OK.
Thanks a lot. I will try next time.
exit status 1
esp8266wifi.h: No such file or directory
Where is the library. Error always comes.
done
hi
i have this on my serie monitor
10:49:10.269 -> ets Jan 8 2013,rst cause:2, boot mode:(3,6)
10:49:10.303 ->
10:49:10.303 -> load 0x4010f000, len 3664, room 16
10:49:10.303 -> tail 0
10:49:10.303 -> chksum 0xee
10:49:10.303 -> csum 0xee
10:49:10.303 -> v39c79d9b
10:49:10.303 -> ~ld
Hi! “cause:2” seems to mean either external reset or waking up from deep sleep (as per https://www.espressif.com/sites/default/files/documentation/esp8266_reset_causes_and_common_fatal_exception_causes_en.pdf). Does this exception frequency correspond to the sleep interval you set in the code? Do you see any other output with #define DEBUG uncommented?
Thanks for the project. I really want to repeat it. But when compiling, it gives an error in: std::string s(reinterpret_cast(payload), length); (153:14: error: variable ‘std::string s’ has initializer but incomplete type
153 | std::string s(reinterpret_cast(payload), length);
| ^
exit status 1
variable ‘std::string s’ has initializer but incomplete type)
How to fix it?
Can you try adding
#include <string>
to the very top of the sketch?
Thanks a lot! There are no errors. Also explain how and where to use mqtt.items: and mqtt.rules:?
Thanks, I’ll update the post!
As for the files you mentioned, they are Openhab config files for MQTT. I’ve switched to Home Assistant a while ago, so those may be outdated, sorry about that.
Thanks for answering. I don’t understand where to insert mqtt.items: and mqtt.rules: code? Where should they be registered or created?
Do you use Openhab and have it running?
Home Assistant (OpenHab)MQTT.
Sorry, I don’t get it: do you use Openhab or Home Assistant?
My MQTT broker is assembled on ESP 12f. I want to use your code to connect to it.
If you put your MQTT broker IP address and port in config.h, the broker will receive and distribute weather data. Then it’s up to your MQTT-compatible smart home system (Openhab/HA/etc.) to grab weather station data and display and process it.
I understand it. I don’t understand what to do with mqtt.items: and mqtt.rules:, the code in the article. I created the config.h
mqtt.items and mqtt.rules are config files for an older version of Openhab. If you use Openhab, unfortunately I cannot help you: I don’t use it any more. If you use Home Assistant, I can try to add a brief howto to the post some time later.
I will be using Home Assistant. I would be very grateful if you write a guide. Thank you for your communication.
Hi there, please check out Home Assistant section I’ve just added.
You can link, I can not find the project.
It’s added to this very post, in the last part of it.
hello, this is very nice project. Can you help me to connect esp with HomeAssistant
I tired to set MQTT_USER and MQTT_PASSWORD but stil not have connection to my MQTT BROKER on HA.
I found it, just add username and password for broker in this line
if (client.connect(chipId.c_str(), username, password, willTopic.c_str(), 1, true, STATUS_DISCONNECTED)
Thanks for reporting this, I’ve added info on MQTT authentication to the main post.