Doorbell Butler

Smart doorbell with notifications and a custom jingle played on Google devices throughout the house

6 minute read

6 min read

My house didn’t have a doorbell when I moved in so I made my own! Instead of the traditional “ding dong” it plays a jingle written by my friend Nick Dahlquist throughout my house on Google Home devices.

The doorbell button is attached to an ESP32 which is connected to my WiFi. I also setup a few Docker containers on a Raspberry Pi to help with the automation.

ESP32

The first piece of this project is the WiFi connected microcontroller which uses MQTT and a tiny speaker to indicate when the doorbell button has been pressed.

Circuit design

R1
[Not supported by viewer]
VCC
[Not supported by viewer]
ESP32
ESP32
GND
[Not supported by viewer]
23
[Not supported by viewer]
DFPLAYER

[Not supported by viewer]
RX
[Not supported by viewer]
TX
[Not supported by viewer]
RX
[Not supported by viewer]
TX
[Not supported by viewer]
VCC
[Not supported by viewer]
GND
[Not supported by viewer]
+
[Not supported by viewer]

[Not supported by viewer]
SDCARD
[Not supported by viewer]

To read the state of the button, I used a pull-up resistor to make the input pin’s value HIGH by default.

SparkFun explains pull-up resistors the best so why reinvent the wheel.

With a pull-up resistor, the input pin will read a high state when the button is not pressed. In other words, a small amount of current is flowing between VCC and the input pin (not to ground), thus the input pin reads close to VCC. When the button is pressed, it connects the input pin directly to ground. The current flows through the resistor to ground, thus the input pin reads a low state. Keep in mind, if the resistor wasn’t there, your button would connect VCC to ground, which is very bad and is also known as a short.1

I put the MP3 of my friend Nick singing on an SD card which I can plug directly into the DFPlayer Mini module. The DFPlayer is controlled through a serial connection which has

Firmware

In the firmware setup, I set pin 23 as an input and I initialize the module used to play sound by specifying serial TX/RX pins. Next, I connect to my house WiFi network and establish a connection with the Raspberry Pi’s MQTT broker. After that’s all done I publish something todoorbell/status just so I can keep track of the device’s status.

The ESP32’s loop checks on WiFi and MQTT connections and reads pin 23’s value. When the button is pressed and it reads LOW, I publish a message on doorbell/ring and tell the DFPlayer to play the only MP3 on the inserted SD card.

#include <WiFi.h>
#include <PubSubClient.h>
#include <DFRobotDFPlayerMini.h>

#define BUTTON_PIN 23

const char* ssid     = "network-name";
const char* password = "network-password";
const char* mqtt_server = "mqtt.broker.ip.address";
const char* mqtt_username = "mqtt-username";
const char* mqtt_password = "mqtt-password";

WiFiClient espClient;
PubSubClient client(espClient);

HardwareSerial dfSerial(2);
DFRobotDFPlayerMini dfPlayer;

long lastRing = 0;
unsigned long checkWifi = 30000;
unsigned long checkMqtt = 30000;

void setupWifi() {
  Serial.println("Connecting to WiFi network: " + String(ssid));

  WiFi.begin(ssid, password);

  Serial.println();
  Serial.println("WiFi connected!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  randomSeed(micros());
}

void setupMqtt() {
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  reconnectMqtt();
}

void reconnectMqtt() {
  String clientId = "doorbell-";
  clientId += String(random(0xffff), HEX);
  
  if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
    client.publish("doorbell/status", "Hello!");
    client.subscribe("doorbell/ping");
  } else {
    Serial.println("Connection to MQTT failed. Trying again in 5 seconds!");
    delay(5000);
  }
}

void setupDfplayer() {
  dfSerial.begin(9600);
  
  Serial.println("Dfplayer connecting...");
  
  if (!dfPlayer.begin(dfSerial)) {
    Serial.println("Dfplayer connection error!");
    while(true);
  }
  
  dfPlayer.volume(25);
}

void setup() {
  Serial.begin(115200);
  
  pinMode(BUTTON_PIN, INPUT);

  setupDfplayer();
  setupWifi();
  setupMqtt();
}

void callback(char* topic, byte* payload, unsigned int length) {
  char s[20];
  sprintf(s, "%u", millis());
  client.publish("doorbell/status", s);
}

void loop() {
  long now = millis();
  
  if ((WiFi.status() != WL_CONNECTED) && (now > checkWifi)) {
    Serial.println("Disconnected from WiFi. Waiting 30 seconds!");
    setupWifi();
    
    checkWifi = now + 30000;
  }

  if ((!client.connected()) && (now > checkMqtt)) {
    Serial.println("Disconnected from MQTT. Waiting 30 seconds!");
    reconnectMqtt();

    checkMqtt = now + 30000;
  } else {
    client.loop();
  }

  if (digitalRead(BUTTON_PIN) == 0 && now - lastRing > 2000) {
    lastRing = now;

    client.publish("doorbell/ring", "Ding dong!");
    dfPlayer.play(1);
  }
}

“Ding Dong”

This speaker is only 0.3 watts so I could hardly hear it.
This speaker is only 0.3 watts so I could hardly hear it.

Initially, I used a DFPlayer mini module to play the mp3 from an SD card on tiny connected speaker. But this wasn’t entirely effective because the speaker wasn’t audible throughout the entire house.

So instead I used Home Assistant to play the mp3 on both of my Google Home devices (one on each floor). The mp3 is locally served by a Raspberry Pi.

Raspberry Pi server

This project relies on three Docker containers running on a Raspberry Pi 3 in my house:

  1. MQTT broker eclipse-mosquitto
  2. Home Assistant instance raspberrypi3-homeassistant
  3. Apache HTTP server httpd

I created these containers with the follow docker-compose.yml:

version: '2.1'
services:
  mosquitto:
    container_name: mosquitto
    image: eclipse-mosquitto
    user: "1000:1000"
    ports:
      - 1883:1883
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /opt/mosquitto:/mosquitto/config:ro
      - /opt/mosquitto:/mosquitto/log
      - /opt/mosquitto:/mosquitto/data
    restart: on-failure
  homeassistant:
    container_name: homeassistant
    image: homeassistant/raspberrypi3-homeassistant:latest
    network_mode: "host"
    volumes:
      - /opt/homeassistant:/config
      - /etc/localtime:/etc/localtime:ro
    restart: on-failure
    depends_on:
      mosquitto:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://127.0.0.1:8123"]
      interval: 30s
      timeout: 10s
      retries: 6
  http-server:
    container_name: http-server
    image: httpd
    ports:
      - 80:80
    volumes:
      - /opt/http-server/www:/usr/local/apache2/htdocs:ro
    restart: always

MQTT Broker

First off, we’ve got the broker to which the ESP32 publishes and Home Assistant subscribes. It’s configuration is pretty straightforward — I used the default port (1883), told it to write logs to the volume Docker provided, and setup authentication to limit the ability to subscribe and publish to any old topic.

Home Assistant

The Home Assistant instance has an automation which subscribes to the doorbell/ring topic and performs a few actions when a message is received. First, it calls the Telegram and IFTTT APIs which sends notifications to our phones. Then it connects to my Google Home devices and instructs them to play the MP3 hosted on the Pi.

Here’s the Home Assistant automation configuration:

id: doorbell_ring
alias: Doorbell alerts
trigger:
- platform: mqtt
  topic: doorbell/ring
action:
- service: notify.telegram
  data:
    message: Someone is here for you.
    title: Ding dong!
- service: ifttt.trigger
  data: 
    event: doorbell_ring
    value1: Ding dong!
- service: media_player.turn_on
  data:
    entity_id: media_player.living_room_speaker
- service: media_player.play_media
  data:
    entity_id: media_player.living_room_speaker
    media_content_id: http://rpi.ip.address/doorbell.mp3
    media_content_type: audio/mp3
- service: media_player.turn_on
  data:
    entity_id: media_player.basement_speaker
- service: media_player.play_media
  data:
    entity_id: media_player.basement_speaker
    media_content_id: http://rpi.ip.address/doorbell.mp3
    media_content_type: audio/mp3

Apache HTTP Server

Lastly, we’ve got the Apache server which hosts a single file: the custom jingle. It’s serving on port 80 so Home Assistant can just provided the IP address of the Pi and the name of the MP3. This required zero configuration aside from placing the file in www directory.