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):
Leave a Reply