DIY ESP32 + ESPHome

BME280 over WiFi, ESPHome YAML

A bring-your-own-hardware path for tinkerers and anyone whose sensor mix is not covered by a commercial unit. Total bill of materials: ≈ €8.

BOM

  • ESP32 dev board: WROOM-32 or ESP32-S3-DevKit, ~€4.
  • BME280 breakout (temperature + humidity + pressure), ~€2.
  • 18650 holder + cell, optional, ~€2.
  • Four jumper wires.

The BME280 talks I²C. Default address on most breakouts is 0x76.

Wire

BME280ESP32 (WROOM)
VCC3V3
GNDGND
SDAGPIO 21
SCLGPIO 22

If you bought the BMP280 by mistake (no humidity, only T + P), the wiring is the same and ESPHome's bmp280 platform works in place of bme280.

Flash ESPHome

Install the ESPHome CLI (pip install esphome or use the Docker image). Save the following to opensense-fridge.yaml:

$ opensense-fridge.yaml

esphome: name: opensense-fridge friendly_name: Walk-in fridge

esp32: board: esp32dev framework: type: arduino

logger: level: INFO

wifi: ssid: !secret wifi_ssid password: !secret wifi_pwd manual_ip: static_ip: 192.168.1.42 gateway: 192.168.1.1 subnet: 255.255.255.0

api: encryption: key: !secret api_key

ota:

  • platform: esphome

i2c: sda: 21 scl: 22 scan: true

sensor:

  • platform: bme280_i2c address: 0x76 update_interval: 60s temperature: name: "Temperature" id: temp humidity: name: "Humidity" id: hum pressure: name: "Pressure" id: pres

http_request: useragent: opensense-esp32 timeout: 10s

interval:

  • interval: 60s then:
    • http_request.post: url: !secret opensense_ingest_url headers: Content-Type: application/json body: |- { "ts": "", "measurements": { "type": "temperature", "value": }, { "type": "humidity", "value": }, { "type": "pressure", "value": } }

Create a secrets.yaml next to it:

$ secrets.yaml

wifi_ssid: 'CafeBratislava' wifi_pwd: 'pourover4eva' api_key: '<32-byte base64 from esphome wizard or openssl rand -base64 32>' opensense_ingest_url: 'https://api.opensense.murzin.digital/v1/ingest?token=ds_live_…&device=fridge01'

Compile and flash:

$ flash

esphome run opensense-fridge.yaml

After the first boot the ESP32 starts POSTing to OpenSense every 60 s. Add the device via + ADD DEVICE → DIY → ESP32 to receive the ingest URL.

Deep sleep (battery operation)

For battery operation, replace the interval block with a deep-sleep strategy:

$ esphome deep-sleep

deep_sleep: run_duration: 8s sleep_duration: 600s # 10 minutes id: ds

Trigger one read+post on boot, then sleep

on_boot:

  • priority: -100.0 then:
    • sensor.template.publish: id: temp state: !lambda 'return id(bme280).temperature->state;'
    • http_request.post: …
    • deep_sleep.enter: ds

A WROOM-32 in this regime draws < 12 µA during sleep; a 3000 mAh 18650 runs for about 6 months at 10-minute cadence. The ESP32-S3 is worse; prefer WROOM-32 or ESP32-C3 for battery sensors.

Multiple probes

The BME280 is one I²C address. To run two BME280s on the same ESP32, solder the SDO line of the second one to 3V3 to switch its I²C address to 0x77. In the YAML:

sensor:
  - platform: bme280_i2c
    address: 0x76
    temperature: { name: "Fridge front" }
  - platform: bme280_i2c
    address: 0x77
    temperature: { name: "Fridge back"  }

In the JSON POST, include a label per measurement so OpenSense can map to two channels:

{
  "measurements": [
    { "type": "temperature", "value": 4.1, "label": "front" },
    { "type": "temperature", "value": 4.3, "label": "back"  }
  ]
}

Calibration

If you need calibrated readings (lab / pharmacy), pair this ESP32 with a calibrated thermistor (PT100 + MAX31865, or a Sensirion SHT45 instead of the BME280). Run a single-point calibration in ice water at 0 °C and a two-point against a calibrated reference. Apply the offset in ESPHome:

sensor:
  - platform: bme280_i2c
    temperature:
      filters:
        - offset: -0.3   # measured at 0 °C against a Fluke 1551A

The OpenSense data model has no concept of "raw vs calibrated" — whatever your device sends is what is stored. If you need to record the calibration event, attach it to the device page as a free-text note. We may formalise calibration as a first-class object later.