ESPHome: CO2 Sensors

I am using an SCD30 connected to a Raspberry PI Zero W to send CO2 measurements every 2 seconds to my Homeassistant. The current value of the SCD30 together with the difference of the last minute is shown in my statusbar, see a previous blog post about this.

But before I migrate the SCD30 to ESPHome I want to try a MH-Z19B I have bought a while ago and never used. First I soldered pins on the sensor and connected them to a Raspberry PI Pico W. For this I used Pins GP0 and GP1 to connect to RX/TX of the sensor. There are a lot of alternative UART pins on the Pico. A common alternative here would be GP21/GP22. The sensor needs 4.5~5.5 V DC so connect to 5V and of course connect GND.

The wiring setup looks like this:


My ESPHome config (ESPHome reference for MHZ19):

  tx_pin: 0
  rx_pin: 1
  baud_rate: 9600

  - platform: mhz19
      name: MH-Z19 CO2 Value
      name: MH-Z19 Temperature

When booting the sensor is warming up and after this we get CO2 values and temperature:

[20:02:29][W][mhz19:035]: MHZ19 warming up, 30s left
[20:03:29][W][component:170]: Component mhz19.sensor cleared Warning flag
[20:03:29][D][mhz19:065]: MHZ19 Received CO₂=756ppm Temperature=23°C Status=0x00
[20:03:29][D][sensor:093]: 'MH-Z19 CO2 Value': Sending state 756.00000 ppm with 0 decimals of accuracy
[20:03:29][D][sensor:093]: 'MH-Z19 Temperature': Sending state 23.00000 °C with 0 decimals of accuracy

On the Homeassistant dashboard the sensor works as expected:


Next the SCD30, which has a lot better accuracy, but costs more than twice as much. The sensor uses I2C which we used before for a BME280. We connect the SCD30 to GP0/GP1 (the same pins as for UART for the MHZ19). Important here is to connect SDA to GP0 and SCL to GP1 and not crosswise as for RX/TX.

Addionally I added a BME280 the same way my Raspberry PI Zero had used before. Because we have two devices that use I2C but can only specify one pin pair per bus we use the second bus for the BME280. We connected the BME280 to GP26 and GP27 for SDA and SCL and for power to the 3.3V pin.

My ESPHome config for the SCD30 (ESPHome reference) and a BME280:

  - id: bus0
    sda: 0
    scl: 1
  - id: bus1
    sda: 26
    scl: 27

  - platform: scd30
    i2c_id: bus0
      name: "Desk CO2"
      accuracy_decimals: 1
      name: "Desk Temperature"
      accuracy_decimals: 2
      name: "Desk Humidity"
      accuracy_decimals: 1
    address: 0x61
    update_interval: 2s
  - platform: bme280_i2c
    i2c_id: bus1
      name: "Desk BME280 Temperature"
      name: "Desk BME280 Pressure"
      name: "Desk BME280 Humidity"
    address: 0x76
    update_interval: 60s

Bootup is a lot faster and there is no initialization wait for the CO2 sensor:

[20:53:29][D][scd30:186]: Got CO2=962.24ppm temperature=25.13°C humidity=50.12%
[20:53:29][D][sensor:093]: 'Desk CO2': Sending state 962.23608 ppm with 1 decimals of accuracy
[20:53:29][D][sensor:093]: 'Desk Temperature': Sending state 25.13084 °C with 2 decimals of accuracy
[20:53:29][D][sensor:093]: 'Desk Humidity': Sending state 50.12207 % with 1 decimals of accuracy
[20:53:34][D][sensor:093]: 'Desk BME280 Temperature': Sending state 22.73586 °C with 1 decimals of accuracy
[20:53:34][D][sensor:093]: 'Desk BME280 Pressure': Sending state 959.16943 hPa with 1 decimals of accuracy
[20:53:34][D][sensor:093]: 'Desk BME280 Humidity': Sending state 53.44043 % with 1 decimals of accuracy

In Homeassistant the card looks like this:


The temperature of the SCD30 is a bit off. This can be corrected with the temperature_offset parameter in the sensor config. But because I have an additional BME280, I don't care about the offset.

Positives of migrating the SCD30 to ESPHome: one Raspberry Pi Zero shut off (no OS updates anymore, less power consumption for a Pico than for a Zero). Addionally I can update the Pico via OTA every few months with probably less issues.

ESPHome: Over The Air Update

At the beginning of 2024 I started using ESPHome to replace Raspberry PI Pico Ws and wrote about it. Then I didn't activate OTA update because I didn't know about it and all the Picos I deployed then are now a bit outdated.

Before changing anything or compiling a firmware we need to update the ESPHome Docker image. This felt a bit strange, because pulling a new image wasn't enough, so I deleted the some generated folders, too.

# update esphome, to get the current firmware code
docker pull esphome/esphome
# delete some cached folders to redownload platformio and guarantee a new compile
sudo rm -rf .esphome/build/pico2 .esphome/platformio

Next is to add ota to the config (best with a password defined in secrets.yaml).

  password: !secret ota_password
The other change I needed was platform: bme280 is named platform: bme280_i2c because this changed in the new ESPHome firmware.
After changing the configuration we compile a new firmware which creates a firmware.uf2 because it is a Raspberry PI Pico.
For me this worked like this:
docker compose run --rm esphome compile pico2.yaml
# mount the Pico before by pressing the button when plugging into the notebook
cp config/.esphome/build/pico2/.pioenvs/pico2/firmware.uf2 /run/media/mfa/RPI-RP2/

When bootet after this we can now update the Pico via OTA and don't need to plug it in again (hopefully ever). The update via OTA for my Pico using docker compose:

docker compose run --rm esphome run pico2.yaml

Because the Pico is not connected to my notebook (anymore) there is only the OTA option and it will update the Pico via OTA automatically. Running the command is very verbose and you see success and the following logs when the Pico is booting again:

INFO Upload took 4.52 seconds, waiting for result...
INFO OTA successful
INFO Successfully uploaded program.
INFO Starting log output from pico2.local using esphome API
[22:49:27][I][app:100]: ESPHome version 2024.5.3 compiled on <...>

In the log output I see the device has booted and is measuring using a BME280 as expected.

Because we can update the Pico now very easy we can add a new sensor that adds the current version as text sensor. The firmware version is already shown in the ESPHome Integration, but with an Entity we can add a reminder for outdated versions to our Dashboards. Adding a firmware version text_sensor is already an example in the documentation, so we add this to the config:

  - platform: version
    name: "ESPHome Version"
    hide_timestamp: true

Compile and run as described above and we will get a new sensor in Homeassistant:


Activating OTA is absolutely worth it. A Pico (or ESP32) can stay in place where ever it is deployed and still gets a new firmware or new settings.

Wayland Copy and Paste

A year ago I started to use Sway as Windowmanager based on Wayland and I wrote about screenshotting. Since then I used wl-copy and wl-paste from wl-clipboard quite a bit.

The obvious ones, copy something into the clipboad at the end of a script. The resulting url or the resulting image:

$ wl-copy < /tmp/screen.png
# or
$ cat /tmp/screen.png | wl-copy
# or for a url / string
$ wl-copy ""

And a more interesting one: Watch for every copy event and append it at the end of a file:

# using cut
$ wl-paste --watch cut -b 1- > all_entries_with_newlines_at_the_end.txt

# the same but using awk
$ wl-paste --watch awk '{print}' > all_entries_with_newlines_at_the_end_awk.txt