Smartifying a 12V AC dumb doorbell with an ESP-01 and ESPNow communication

Hello,

This is my first integration into Home Assistant, in a way I haven’t seen done before.

The idea is to have an ESP-01 in parallel with the doorbell chime, so that when you press the doorbell button, both get powered up. While it’s on, the ESP-01 sends a message to Home Assistant and in turn, Home Assistant sends notifications.

So far so good, but a couple of issues:

  • The ESP-01 may need to be powered for longer than the amount of time people press the button.
  • 12V AC? The ESP01 is 3.3V with no current protection.

So that’s what my ugly circuit board does. The 12V AC is converted into 3.3V using a bridge rectifier, a large capacitor (1000uF is what I had on hand), an AMS1117-3.3 and a smaller capacitor (33uF) to handle peak current from the ESP-01. This gives me an “energy budget” large enough to keep my ESP-01 powered for just under a second.

The next question is, what can we do with an ESP-01 powered for under a second? Certainly not WiFi, but with ESPNow (Espressif’s own communication protocol), my ESP01 can connect and send packets to another ESP8266 in under 100ms. All you need for ESPNow to work is on the “sender” to know the MAC address of the “receiver”. I haven’t tried encrypted packets yet, so I don’t know if my “energy budget” will allow for it. But with clear comms, I haven’t had any failures to communicate (yet).

OK, we have an ESP-01 that can talk to another ESP8266 or ESP32, how does that get us closer to Home Assistant?

I chose to create a serial device for my espnow_hub (a nodemcu connected to the HA server via /dev/ttyUSB2) and a binary sensor for the doorbell.

I haven’t really thought about what it is I want to communicate beyond toggle and push buttons, so the following leaves quite some room for improvement!

The strings sent to HA over serial are like this:

A0:20:A6:12:4B:FE|2
A0:20:A6:12:4B:FE|0

Left of the | is the MAC address of the sender, right of it is the payload. I have 3 possible payloads:

  • 0 is off
  • 1 is on
  • 2 is momentary on

2 is a special case: If the “receiver” sees a payload of 2 (example above), it sends it to HA over serial just like it would for a payload of 0 or 1, but then also sends a second string after 100ms which fakes a payload of 0 for “off”. This is to keep the HA template as simple as possible. I have two devices I am listening to (‘A0:20:A6:12:4B:FE’ and ‘A0:20:A6:12:4B:FF’) . One is the actual ESP-01 I use in my chime, the other one is a NodeMCU that will send a payload of 2 if I press its flash button.

# Loads default set of integrations. Do not remove.
default_config:

# Load frontend themes from the themes folder
frontend:
  themes: !include_dir_merge_named themes

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml

sensor:
  - platform: serial
    name: espnow_hub
    serial_port: /dev/ttyUSB2
    baudrate: 115200

binary_sensor:
  - platform: template
    sensors:
      doorbell_sensor:
        unique_id: "doorbell"
        friendly_name: "Doorbell"
        value_template: >
          {% set d = states('sensor.espnow_hub').split('|') %}
          {% if d and (d[0] == 'A0:20:A6:12:4B:FE' or d[0] == 'A0:20:A6:12:4B:FF') %}
            {% if d[1] == '2' or d[1] == '1' %}
              true
            {% else %}
              false
            {% endif %}
          {% endif %} 

Now, let’s talk about the arduino code.

My espnow_hub is just a nodemcu connected to the server via serial. I installed screen on the HA server, so I could do a screen /dev/ttyUSB2 115200 and see packets being received.

Here's the PlatformIO Arduino code for the receiver

platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[platformio]
default_envs = nodemcu

[env:esp01]
platform = espressif8266
board = esp01
framework = arduino
upload_speed = 921600
upload_port = COM32
monitor_port = COM32
upload_resetmethod = nodemcu
monitor_speed = 115200
lib_deps = 
	regenbogencode/ESPNowW@^1.0.2
	sstaub/Ticker@^4.4.0
build_flags = 
    '-D RECEIVER_MAC={0xA0, 0x20, 0xA6, 0x12, 0x4B, 0xFC}'

[env:nodemcu]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
monitor_speed = 115200
upload_port = COM7
monitor_port = COM7
lib_deps = 
	regenbogencode/ESPNowW@^1.0.2
	sstaub/Ticker@^4.4.0
build_flags =
    '-D RECEIVER_MAC={0xA0, 0x20, 0xA6, 0x12, 0x4B, 0xFC}'

main.cpp

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "ESPNowW.h"
#include <EasyButton.h>

//The sender needs the receiver's mac address
uint8_t receiver_mac[] = RECEIVER_MAC;

// put function declarations here:
char macStr[18];

// Arduino pin where the button is connected to.
#define BUTTON_PIN 0

// Instance of the button.
EasyButton button(BUTTON_PIN);

// Callback function to be called when the button is pressed.
void onBtn() {
  digitalWrite(D0, LOW);
  Serial.print(WiFi.macAddress());
  Serial.println("|MACADDR");
  delay(100);
  digitalWrite(D0, HIGH);
}

//Here we build a nice message for Home assistant
//
//This handles a couple of different scenarios according to the first byte of payload
//MM:AA:CC:MM:AA:CC|1 and MM:AA:CC:MM:AA:CC|0 is a switch
//MM:AA:CC:MM:AA:CC|2 is a momentary switch. The receiver then fakes the MM:AA:CC:MM:AA:CC|0 message
//MM:AA:CC:MM:AA:CC|4|0|1|2|3|... 4 is a sensor and the rest comes in as uint8 HA will split and check the payload
//A bit messy? Allows ESP8266s to send single bytes payloads (especially the doorbell that isn't powered for long)
void onRecv(uint8_t *mac_addr , uint8_t *data , uint8_t data_len)
{
  digitalWrite(D0, LOW);
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0] , mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);

  Serial.print(macStr);

  for(int i = 0 ; i < data_len; i++)
  {
    Serial.printf("|%d" , data[i]);
  }

  Serial.println();

  //Here we simulate the sensor going back to 0 when the (single byte) payload is 2 (defined as momentary switch)
  if (data[0] == 2 && data_len == 1) {
    delay(100);
    Serial.print(macStr);
    Serial.println("|0");
  }
	digitalWrite(D0, HIGH);
}

void setup() {
  pinMode(D0, OUTPUT);
  digitalWrite(D0, HIGH);

  Serial.begin(115200);
  Serial.println();

  #ifdef DEBUG
    Serial.print("ESP8266 Board MAC Address:  ");
    Serial.println(WiFi.macAddress());
    Serial.println("ESP8266 RECEIVER");
  #endif

  // Display the mac address
  onBtn();

  // Initialize ESPNow
  WiFi.mode(WIFI_STA);
  wifi_set_macaddr(STATION_IF, &receiver_mac[0]);  
  WiFi.disconnect();
  ESPNow.init();
  ESPNow.reg_recv_cb(onRecv);

  // Initialize the button.
  button.begin();
  // Add the callback function to be called when the button is pressed.
  button.onPressed(onBtn);  
}

void loop() {
  // Continuously read the status of the button. 
  button.read();
}

The MAC address of the receiver is spoofed (much easier for testing) and is displayed on the serial port when the “flash” button is pressed on the nodemcu.

The code for the sender is equally as simple.

Here's the PlatformIO Arduino code for the sender

platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp01]
platform = espressif8266
board = esp01_1m
framework = arduino
upload_speed = 921600 
upload_port = COM6
monitor_port = COM6
upload_resetmethod = nodemcu
monitor_speed = 115200
lib_deps =
    regenbogencode/ESPNowW@^1.0.2
	sstaub/Ticker@^4.4.0

build_flags =
    '-D RECEIVER_MAC={0xA0, 0x20, 0xA6, 0x12, 0x4B, 0xFC}'
    ;'-D DEBUG'

[env:nodemcu]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
monitor_speed = 115200
upload_port = COM9
monitor_port = COM9
lib_deps = 
	regenbogencode/ESPNowW@^1.0.2
	sstaub/Ticker@^4.4.0
build_flags =
    '-D RECEIVER_MAC={0xA0, 0x20, 0xA6, 0x12, 0x4B, 0xFC}'
    '-D DEBUG'

main.cpp:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "ESPNowW.h"

//The sender needs the receiver's mac address
uint8_t receiver_mac[] = RECEIVER_MAC;

//Payload. Let's say a toggle button sends a 1 for on and a 0 for off
//A payload of 2 is for a momentary switch
//A payload of 4 indicates a sensor and the data is sent as uint* bytes
uint8_t payload = 2;

//========================================
#ifdef DEBUG
#include <EasyButton.h>

// Arduino pin where the button is connected to.
#define BUTTON_PIN 0

// Instance of the button.
EasyButton button(BUTTON_PIN);
#endif
//========================================


// Callback function to be called when the button is pressed.
void onBtn() {
  ESPNow.send_message(receiver_mac , &payload , 1);
//========================================
  #if defined DEBUG
    digitalWrite(D0, LOW);
    Serial.print("Sending payload:  ");
    Serial.println(payload);
    delay(100);
    digitalWrite(D0, HIGH);
  #endif
//========================================
}

void setup() {
//========================================
  #ifdef DEBUG
    pinMode(D0, OUTPUT);
    digitalWrite(D0, HIGH);

    Serial.begin(115200);
    Serial.println();
    Serial.print("ESP8266 Board MAC Address:  ");
    Serial.println(WiFi.macAddress());
    Serial.println("ESP8266 SENDER");
  #endif
//========================================

  // Disable WIFI to use ESPNow and add the receiver's MAC as a peer
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  ESPNow.init();
  ESPNow.add_peer(receiver_mac);

//========================================
  #ifdef DEBUG
  // Initialize the button.
  button.begin();
  // Add the callback function to be called when the button is pressed.
  button.onPressed(onBtn);  
  #endif
//========================================
}

void loop() {
//========================================
#ifdef DEBUG 
  // Continuously read the status of the button. 
  button.read();
#else
  onBtn();
  delay(1000);
#endif
//========================================
}

It’s as bare as possible in its non-debug version and will send its first payload as soon as it’s powered on, then once every second. That’s how I know my energy budget is only enough to run the code for under a second.

Once all is in place, I can see my entities:

and the automation side is really basic right now (just got into HA).

- id: '1718431786475'
  alias: Doorbell Automation
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.doorbell_sensor_2
    from: 'off'
    to: 'on'
  condition: []
  action:
  - service: notify.notify
    metadata: {}
    data:
      message: Someone at the door!
      data:
        channel: General
        importance: high
        visibility: public
  mode: single

That’s all I can say about my first HA project. Any comments, suggestions or improvements welcome. This is all released under the “do as you please” WTFPL public license :slight_smile:

Sorry, I will add pictures when I’m allowed to. Totally understandable!

Cheers,
John

Sorry for the self reply. Here’s a photograph of the sender board, connected in parallel with the chime.

The ESPNow “hub” (the receiver) is just a ESP8266 NodeMCU connected to the HA server via USB.

Cheers,
John

A friedland type4, popular doorbell. Perhaps a soft latching switch could be employed.I struggled a long time to do anything with mine. Ended up using optocouplers and 433mhz sender.

Hi Spiro,

You have good eyes! And I agree, the dumbest doorbell circuit is a real head scratcher in terms of automation. What can you do when the chime is only powered on when the doorbell button at the door is pressed? All the cables in my house are buried deep inside the walls, so that’s all I had to work with.

I think that a lit button may make things slightly easier, as there must be a small amount of current always flowing through the chime to keep the lamp / LED in the doorbell button lit up.

Would that be enough to keep a microcontroller also permanently on if it was in parallel with the chime?

My other thought is that in my circuit, if the input (smoothing / sink) capacitor is too large, then maybe that would steal enough current to make the chime noticeably quieter.

I thought it did happen in my case, but it was just decades of accumulated rust in the doorbell button. Sandpaper solved that problem! :wink:

Cheers,
John


This is mine. It runs a front and rear door. Works well so even the briefest touch of the button sets it off. It sets off a door sensor by replacing the reed switch.

1 Like