I’ve just got my first ESPHome project working to my satisfaction and I thought I’d post a success story along with a bunch of tips, tricks, and traps I’d encountered along the way. I’m using an Adafruit QT Py ESP32-C3 board with a PiicoDev Atmospheric Sensor BME280. I also tried Adafruit AHT20 and Adafruit HTU31 Temperature & Humidity Sensors but they don’t seem to be supported by ESPHome yet (and don’t have the nice pressure sensor).
First, my config (sorry for the size, explanations will follow);
esphome:
name: outside
platformio_options:
board_build.flash_mode: dio
esp32:
# At some point the actual board might be supported.
# board: adafruit_qt_py_esp32-c3
board: esp32-c3-devkitm-1
framework:
type: esp-idf
variant: ESP32C3
# Enable logging
logger:
ota:
password: "<password>"
mqtt:
broker: 192.168.7.6
username: "mqtt"
password: "<password>"
discovery: true
discovery_retain: true
discovery_unique_id_generator: mac
discovery_object_id_generator: device_name
birth_message:
will_message:
shutdown_message:
on_message:
- topic: esp/sleep_mode
payload: 'OFF'
then:
- deep_sleep.prevent: deep_sleep_1
- topic: esp/sleep_mode
payload: 'ON'
then:
- deep_sleep.allow: deep_sleep_1
wifi:
ssid: "ngurra"
password: "<password>"
fast_connect: true
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Underhouse Fallback Hotspot"
password: "<password>"
i2c:
sda: 5
scl: 6
scan: true
id: bus_a
# We need to use globals to share sensor state without circular references.
globals:
- id: temperature
type: double
restore_value: no
initial_value: 'NAN'
- id: pressure
type: double
restore_value: no
initial_value: 'NAN'
- id: humidity
type: double
restore_value: no
initial_value: 'NAN'
# Example configuration entry
sensor:
- platform: uptime
name: "system uptime"
- platform: wifi_signal
name: "wifi strength"
update_interval: 60s
expire_after: 90s
- platform: template
name: "Dew Point Temperature"
id: dewpoint_temperature
filters:
- lambda: |-
return (243.5*(log(id(humidity)/100)+((17.67 * x)/
(243.5 + x)))/(17.67-log(id(humidity)/100)-
((17.67 * x)/(243.5 + x))));
icon: 'mdi:thermometer-alert'
unit_of_measurement: °C
update_interval: never
expire_after: 90s
- platform: template
name: "Sea Level Pressure"
id: sealevel_pressure
filters:
- lambda: |-
const float STANDARD_ALTITUDE = 34.0; // in meters, see note
return x / powf(1 - ((0.0065 * STANDARD_ALTITUDE) /
(id(temperature) + (0.0065 * STANDARD_ALTITUDE) + 273.15)), 5.257); // in hPa
unit_of_measurement: 'hPa'
update_interval: never
expire_after: 90s
- platform: template
name: "Absolute Humidity"
id: absolute_humidity
filters:
- lambda: |-
const float mw = 18.01534; // molar mass of water g/mol
const float r = 8.31447215; // Universal gas constant J/mol/K
return (6.112 * powf(2.718281828, (17.67 * id(temperature)) /
(id(temperature) + 243.5)) * x * mw) /
((273.15 + id(temperature)) * r); // in grams/m^3
accuracy_decimals: 2
icon: 'mdi:water'
unit_of_measurement: 'g/m³'
update_interval: never
expire_after: 90s
- platform: bme280
temperature:
name: "Temperature"
id: bme280_temperature
oversampling: 4x
expire_after: 90s
on_value:
then:
- lambda: |-
id(temperature) = x;
pressure:
name: "Pressure"
id: bme280_pressure
oversampling: 4x
expire_after: 90s
on_value:
then:
- lambda: |-
id(pressure) = x;
humidity:
name: "Humidity"
id: bme280_humidity
oversampling: 4x
expire_after: 90s
on_value:
then:
- lambda: |-
id(humidity) = x;
id(dewpoint_temperature).publish_state(id(temperature));
id(sealevel_pressure).publish_state(id(pressure));
id(absolute_humidity).publish_state(id(humidity));
address: 0x77
update_interval: 60s
deep_sleep:
id: deep_sleep_1
run_duration: 1s
sleep_duration: 54s
The first problem I encountered is the ESP32-C3 platform is pretty new and not that smoothly supported yet. I like the RISCV architecture from an academic/license/etc point of view, like the low power, and the Adafruit QT Py ESP32-C3 boards are nice. I quickly realised I needed the dev version of ESPHome (maybe it’s changed already). The ESPHome wizard didn’t know about my particular board:
so I had to pick a generic one with the ESP32-C3, and it took a bit of googling to find the platformio_options:
, framework:
, and variant:
settings to make it work.
I have no idea how to setup the dev version of ESPHome inside Home Assistant, but even if I did, I have it running on a RasberryPi3, and apparently the esp-idf framework is not quite ready to run on arm64 and even the dev version doesn’t work yet. So I have ESPHome setup and running on my Debian intel laptop, which also made plugging into the board for the initial upload easy. I run the esphome dashboard and connect to it from the browser on the same machine. The ability to edit, upload, and see logs is really nice.
Apparently you can also use the arduino framework:
and it’s necessary to make the LED work on this board because it uses some neo-bus thingy that the idf framework doesn’t support yet. However, I got the impression that the esp-idf framework is the future and I didn’t really care about making the LED work.
Next was getting i2c and the bme280 working. It took a bit of digging though datasheets to find the particular i2c pins on this board for the i2c:
settings, and adding the basic platform: bme280
sensor entry. I then found the neat bme280 environment suggestion and added the platform: template
entries it suggests for Absolute Humidity, Sea Level Pressure, and and Dew Point Temperature. At this point is was basically working. I set up two of them and Home Assistant could see them both.
At this point I moved the board and sensors into a case I’d designed in onshape and 3D printed. This quickly revealed that without using deep_sleep to reduce power there was a definite ~1degC temperature offset. I guess I could redesign the case to thermally partition the sensor and micro, but I was also considering battery-power and deep_sleep seemed like the right answer. This added a heap of gotchas.
First, you quickly discover that the Home Assistant API is deep_sleep-angry. This API has HA polling the device, so the device needs to stay awake long enough for HA to catch it, which pretty much undermines the whole point of deep_sleep. The alternative API with the right “device-polls-server” needed for deep_sleep is mqtt. So the api:
setting had to go and was replaced by mqtt:
.
At this point, for some strange reason my ESPHome started having problems uploading the new firmware. I was getting some kind of “timeout verifying firmware size” type error. I have no idea what caused it. I initially thought maybe the firmware was too big, but that can’t be right. In the end fiddling with the config, disabling things and retrying until it worked seemed to resolve it, and I’ve had no problems since.
Setting up HA with mosquitto and the mqtt addon was a bit fiddly, but mostly went according to the instructions. These instructions said to add a user for mqtt, so I did, and setup the mqtt addon to login using that user, but later on I removed/re-added the addon (trying to clear stale things in HA) and it was setup using a completely different login. both worked. Initially I setup the mqtt:
entry with empty username:
and password:
entries but that gave errors connecting to the mqtt server. This is a private/firewalled/unimportant wifi network so I initially didn’t really want the hassle of username/password logins, but I have no idea if it’s actually possible to setup mqtt without it.
HA gets pretty messed up when you remove, add, and rename sensors, and cleaning out stale entries seems to be mostly a case of fiddling around, restarting, trying stuff, restarting. removing and reinstalling stuff, restarting…ugh. It’s complicated by mqtt “retained” messages. That discovery_retain: true
setting means discovery messages sent by devices for sensors are retained and regurgitated by the mqtt server whenever HA reconnects. This is great for quickly restoring sensors after HA restarts, but it also means it never forgets old sensors.
This is where I really needed mqtt debugging tools. In HA under “Settings; Devices & Services; Mosquitto broker; configure” you can publish and listen to messages. Listening to “#” will show you everything, “homeassistant/#” shows discovery messages, and <device>/#
shows you status and sensor messages from a particular device. Note the “Retained: true” status on retained messages. The publishing support on HA doesn’t seem to support sending retained messages or specifying the qos. For that I discovered and used the following for deleting retained messages. The topic you’ll mostly want to delete is old homeassistant/sensor/<device>/<sensor>/config
autodiscovery messages.
$ apt-get install mosquitto-clients
$ mosquito_pub -h homeassistant.localnet -u mqtt -P <password> -t <topic> -r -n -d
I’m pretty sure the mosquitto-clients package includes some kind of mqtt listener, but I’ve just been using the one in HA. You’ll pretty quickly get sick of repeating most of these arguments, so set up a `~/.config/mosquitto_pub file with the arguments you keep repeating. Make sure you set it readable only by yourself to keep the password secure.
The main thing that was not working at this point is HA was refusing to see the sensors on my second device. The mqtt addon was seeing the device, but none of it’s sensors. Eventually I discovered HA’s “Settings; System; Logs” which told me HA was complaining about duplicate sensors. Note I thought this might have been related to mqtt retained messages and was looking for the mosquitto logs, and these are not that. I never did find the mosquitto logs, but the duplicate sensors hint led me to the discovery_unique_id_generator: mac
and discovery_object_id_generator: device_name
settings.
The problem is the default sensor id generation just uses the sensor’s name:
prefixed with “ESP<component_type>” as the sensor identifier, so multiple devices with the same config have sensors that clash names. Note that the device name takes the esphome:
name:
setting which is different, so HA sees the different devices, just not the other device’s sensors. Using discovery_unique_id_generator: mac
puts the device MAC address in the name, and discovery_object_id_generator: device_name
seems to result in HA putting the device name into the sensor’s “Entity ID” instead of appending incrementing numeric suffixes onto each one. So sensor.outside_wifi_strength
instead of sensor.wifi_strength_2
, but note that HA seems to remember the entity names so you’ll need to do the HA delete/restore addins/devices/sensors and mqtt-delete-retained dance to make it pick them up.
This is getting long… perhaps I’ll post this now and followup with more in other posts…