IoT: Door Bell Hack

This is how I made my standard old fashioned doorbell MQTT and WIFI capable.
If the door bell rings, a MQTT signal is emitted on the bus. Other consumers take this signal and trigger actions e.g. taking a photo, sending pushover notifications and some other undisclosed stuff ;-)

Basics

  • Take door bell wires, connect to small rectifier, since door bells typically use AC 9V
  • Rectified current triggers a relais connected to ESP8266 chip
  • Relais pulls interrupt pin to 5V in case somebody rings at the door
  • Otherwise interrupt pin is pulled to GND
  • Publishes MQTT signal on the bus if bell rings
  • Software update OTA via WIFI

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "ESP8266HTTPClient.h"
#include "ESP8266httpUpdate.h"

/*
commands
  sensors/doorbell/version
  sensors/doorbell/flash {"http://__REMOVED_/foo.bin"}
Events
  sensors/doorbell {started}
  sensors/doorbell/health {online, offline}
  sensors/doorbell/rssi {int32}
  sensors/doorbell {ring}

  Written by Marco Maniscalco
*/

const char* FIRMWARE_VERSION = "1.2";

const char* ssid     = "__REMOVED__";
const char* password = "__REMOVED__";
const char* mqtt_server = "mqtt";

const char* name_mqtt = "doorbell";
const char* name = "sensors/doorbell";
const char* name_subs = "sensors/doorbell/#";
const char* version = "sensors/doorbell/version";
const char* reset = "sensors/doorbell/reset";
const char* flash = "sensors/doorbell/flash";
const char* flash_result = "sensors/doorbell/flash-result";
const char* name_rssi = "sensors/doorbell/rssi";

const int PIN_LED_RING = 1;
const int PIN_LED_WIFI = 14;
const int PIN_BELL = 12;

char clientRSSI[50];

void callback(char* topic, byte* payload, unsigned int length);

WiFiClient espClient;
PubSubClient client(mqtt_server, 1883, callback, espClient);

void callback(char* topic, byte* payload, unsigned int length) {

    char message_buff[100];
    for(int i=0; i<length; i++) {
      message_buff[i] = payload[i];
    }
    message_buff[length] = '\0';
  
    if(strcmp(topic, flash) == 0) {
      ESPhttpUpdate.rebootOnUpdate(true);
      ESPhttpUpdate.update(message_buff);
    }
    if(strcmp(topic, reset) == 0) {
      ESP.restart();
    }
}

void reconnectWifi() {

    digitalWrite(PIN_LED_WIFI, LOW);

    WiFi.begin(ssid, password);  
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }

    String str = String(WiFi.RSSI());
    str.toCharArray(clientRSSI, str.length()+1); 
}

void reconnectMQTT() {
  while (!client.connected()) {
    if (client.connect(name_mqtt,"sensors/doorbell/health", 0, true, "offline")) {
      client.publish(name_rssi, clientRSSI);   
      client.publish(name, "started");
      client.publish("sensors/doorbell/health", "online", true); delay(0);      
      client.publish(version, FIRMWARE_VERSION);
      client.subscribe(name_subs);
      
      digitalWrite(PIN_LED_WIFI, HIGH);
    } else {
      delay(2000);
      digitalWrite(PIN_LED_WIFI, LOW);
    }
  }
}

volatile byte state = LOW;
volatile bool ring = false;

void blink() {

  // do not ask interrupt pin for at least 2000 ms after boot
  if(millis() < 2000)
    return;

  // read bell pin, this is put to 5v when ring ant to GND otherwise
  state = digitalRead(PIN_BELL);

  // just set volatile status variable
  // do not mess with TCP/IP stack in this interrup routine
  if(state == HIGH) {

    // handled in main loop
    ring = true;
    digitalWrite(PIN_LED_RING, HIGH);
  } 
  else
    digitalWrite(PIN_LED_RING, LOW);
}

void setup() {
  Serial.begin(115200);

  pinMode(PIN_LED_RING, OUTPUT);
  pinMode(PIN_LED_WIFI, OUTPUT);

  pinMode(PIN_BELL, INPUT);

  // PIN_BELL will wake up processor using interrupt
  attachInterrupt(digitalPinToInterrupt(PIN_BELL), blink, CHANGE);
}

void loop() {
  
  if(WiFi.status() != WL_CONNECTED) {
    reconnectWifi();
  }

  if (!client.connected()) {
    reconnectMQTT();
  }

  if(ring) {
    // maybe better ... de-bounce signal to have better quality and less flipping
    ring = false;
    client.publish(name, "ring");
  }
  
  client.loop();
  delay(0);  
}