Updated:

esphome is a framework for ESP32 and ESP8266 with which you can create simple devices by just writing YAML files instead of writing code. I choose esphome when the device I need is simple because, in simple devices, I do not have to write same code again and again. esphome provides components for common tasks, such as OTA, Web interface of the device, and Home Assistant integration.

I need a doorbell-over-WiFi that does a simple thing, notifying me when pressed. I used a slack channel for notifications from my devices. That means, the device should send a message to the channel, and my phone pops up a notification on screen.

Requirements

  • it posts a message to a slack channel when a button is pressed
  • it works on a battery so that I can install it anywhere
  • it is a simple device that anyone can build in a few minutes as an example of esphome

From the requirements, you definitely need to deep sleep the device. ESP8266 is a WiFi device, and WiFi requires more power than other wireless protocols. The device should wake up only when the button is pressed. Otherwise, sleep.

I chose slack for the notification channel, but many chat services provide APIs to post messages. With small modifications to the configuration, it should be easy to post messages to other services.

ESP8266 and deep sleep

ESP8266 in deep sleep consumes very few power, like a few micro ampere. You can run ESP8266 on battry more than a year (How to Run Your ESP8266 for Years on a Battery).

To enable deep sleep, you need to connect GPIO16 to RST. On NodeMCU, GPIO16 is labeled as D0. During the sleep, the voltage is HIGH.

To wake up the device, you need a rising edge signal on RST pin. As the voltage is HIGH in deep sleep, you need to pull the voltage of the pin to LOW, and then HIGH. This can be implemented with a simple button (normally open).

A pin of the button is connected to GPIO16 and RST, and another to GND. When the button is not pressed, the voltage is HIGH. When pressed, the voltage is LOW, and when released, the voltage is HIGH. This creates a rising signal.

As a feedback and for debugging, an LED should indicate request status. Say, short blinks on success, and a longer blink on failure.

Materials

  • a ESP8266 development board
  • a USB cable
  • an LED and a resistor to limit current through the LED (1K ohm should work fine)
  • a tact switch, or a button
  • some wires and a breadboard

Schematic

esp-doorbell schematic
the schematic of esp-doorbell

The esphome configuration

Here, I will explain the configuration sections. The latest code in GitHub can be found at my GitHub repository.

Each section has links to the official documentation. Remember to read them.

Substitutions section

---

# common strings used throughout the program. something unique to the device.
substitutions:
  myname: backgarden-doorbell-1
  location: "backgarden"

substitutions is used to replace strings. They are like macros. You can reference them in the configuration. My devices always have location in substitutions because I need to know device’s location, and embed the location in messages or logs. To reference them, use ${location}. When esphome parses and compiles the configuration, ${location} is replaced with its value.

See the official documentation at Substitutions.

esphome section

This is the core of the configuration. All the logics to meet the requirements are implemented here.

esphome:
  name: ${myname}
  on_boot:
    # post a message to slack upon boot. with priority higher than 200, the
    # network is available.
    # see:
    # https://esphome.io/components/esphome.html#on-boot
    # https://esphome.io/components/http_request.html
    priority: 200
    then:
      - http_request.post:
          # the URL of slack web hook.
          url: !secret slack_webhook_url_esphome_doorbell
          # disable TLS verification because it does not work on ESP8266.
          verify_ssl: false
          headers:
            Content-Type: application/json
          json:
            # see how to format at:
            # https://api.slack.com/reference/surfaces/formatting
            text: !lambda |-
              snprintf(id(buf), sizeof(id(buf)), "@here Someone pushed me at %s", "${location}");
              return id(buf);
            # enable group mention, makes `@here` work
            link_names: "true"
          # give feedback to users. was it successful?
          on_response:
            then:
              - if:
                  condition:
                    lambda: "return status_code == 200;"
                  # when success, blink the LED twice
                  then:
                    - output.turn_on: led
                    - delay: 0.3s
                    - output.turn_off: led
                    - delay: 0.3s
                    - output.turn_on: led
                    - delay: 0.3s
                    - output.turn_off: led
                  # when error happens, blink the LED once, and longer
                  else:
                    - output.turn_on: led
                    - delay: 3s
                    - output.turn_off: led
              # then, regardless of status code, deep sleep.
              - deep_sleep.enter:
                  id: deep_sleep_1

esphome component has several options to configure, and here, I need on_boot, which is an Automation that runs on boot. When the device boots, on_boot runs once. It then post a HTTP request to slack web-hook URL.

The web-hook URL is encrypted because anyone with the URL can post messages to the slack channel, which is not what you want.

http_request.post is an action that posts HTTP requests. The body is a JSON object, and the HTTP header must include Content-Type: application/json. The JSON object is an API call to slack API. The API are documented at Reference: Message payloads. @here is used in the message text so that slack application on mobile devices or web browser application will notify me of the message.

When http_request.post has received a HTTP response from the API, the device blinks a LED, short blinks on success, and longer blinks on failure.

It then enters into deep sleep by calling deep_sleep.enter action.

See the official documentation at ESPHome Core Configuration, and Automations and Templates.

Other sections

esp8266:
  board: nodemcuv2

esp8266 section configures architecture-specific settings. At least, you need to specify board.

See the official documentation at Generic ESP8266.

logger:
  level: debug

debug:
  update_interval: 5s

It is always a good idea to enable debug log. You can see logs over WiFi, or serial port.

See the official documentation at Logger Component and Debug Component.

api:
  password: !secret api_password

api component enables the device to communicate to other devices, such as esphome web UI, or Home Assistant. Use password and protect the communication.

See the official documentation at Native API Component

# OTA is enabled here but it does not work because the device is almost always
# in deepsleep.
ota:
  password: !secret ota_password

With ota component, it is possible to update firmware over WiFi. However, as documented in the comment above, it is very difficult to use OTA if the device is sleeping.

See the official documentation at OTA Update Component.

# the WiFi SSID and password for your network.
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

wifi component configures WiFi and network settings. Simply provide ssid and password.

See the official documentation at WiFi Component.

globals:
  # a buffer to hold the message string because snprintf(3) is used to create
  # the message.
  #
  # you may remove this and snprintf(3) above if the message is a static,
  # fixed message.
  - id: buf
    type: char[512]
    restore_value: no
    initial_value: ""

globals is a section to define global variables that you can use in code. Because the message is dynamically created, you need a variable to keep the string.

See the official documentation at Global Variables.

# to post HTTP requests, you need http_request component.
http_request:
  useragent: esphome/${myname}
  timeout: 10s

http_request component is required to sent HTTP requests.

See the official documentation at HTTP Request.

text_sensor:
  - platform: wifi_info
    ip_address:
      name: ESP IP Address ${myname}
    ssid:
      name: ESP Connected SSID ${myname}
    bssid:
      name: ESP Connected BSSID ${myname}
    mac_address:
      name: ESP Mac Wifi Address ${myname}
    scan_results:
      name: ESP Latest Scan Results ${myname}

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Sensor ${myname}"
    update_interval: 60s

  - platform: uptime
    name: Uptime Sensor ${myname}

wifi_info text_sensor and wifi_signal sensor publish WiFi-related diagnosis information. Useful for debugging. All my devices have these. The information is published to Home Assistant, and you can see it on the web interface.

See the official documentation at WiFi Info Text Sensor, WiFi Signal Sensor, and Uptime Sensor.

# the pin to give feedback with an LED.
output:
  - platform: gpio
    pin: D6
    id: led
    inverted: false

gpio output is a component to control GPIO pins. The pin is used to give HTTP status. The component has an id, and the value is led. This id is required to reference this component from other part of the code, http_request.post action in this example.

See the official documentation at GPIO Output.

deep_sleep:
  id: deep_sleep_1
  # sleep indefinitely. the max value of sleep_duration on ESP8266 is about 3
  # hours 46 minutes. a value bigger than this makes ESP8266 in deep sleep
  # indefinitely.
  sleep_duration: 4h

deep_sleep component is used to enter into deep sleep. The component has id because the component is referenced from the on_boot automation. As I would like to keep the device in deep sleep until someone presses the button, the sleep_duration is longer than the max value of sleep_duration.

See the official documentation at Deep Sleep Component.

Limitations

The device needs a few seconds to post a message because it needs to boot, configure the WiFi interface, obtain an IP address from DHCP server. It is mostly harmless in this application because I do not need real time notifications. A few seconds delay is acceptable. If you need quick response, here is some ideas.

Using static IP address reduces the delay because the devices does not ask DHCP server for an IP address. However, you need to configure a static IP address for the device, and maintain it. To use a static IP address, see manual_ip option in WiFi Component.

Disabling WiFi scanning also reduces the delay because it skips scanning for WiFi access points. See fast_connect option in WiFi Component.

Using a power source, and disabling deep sleep. The device is up and running all the time, and the response is immediate. However, you have to give up battery operation, and the configuration must be modified.

Final words

Building an esphome device is easy. However, that does not necessarily mean you don’t have to lean C or C++. Some concepts require understanding of C or C++. esphome looks simple but it can implement complicated logics, too. You need to understand event-driven programming.

Always read the official documentation. Their documentation is generally good (except internal APIs).

Happy hacking!