Files
2022-12-01 15:55:22 +01:00

486 lines
19 KiB
HTML

<!doctype html>
<html lang="de">
<head>
<title>WiFi Doorbell with ESP32</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta property="og:url" content="http://tilman.de/projekte/wifi-doorbell/" />
<meta property="og:title" content="A WiFi Doorbell" />
<meta property="og:description" content="When moving into an old house, you have to adapt. In our case, there was a bell button at
the gate but the cable had been cut off right after where it enters the house in the basement. With a little programming and my
shiny new 3D printer, we can now abandon the ugly wireless doorbell we used until now." />
<meta property="og:image" content="http://tilman.de/projekte/wifi-doorbell/images/IMG_20181009_090614.jpg" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<link rel="stylesheet" href="fancybox/source/jquery.fancybox.css?v=2.1.5" type="text/css" media="screen" />
<script type="text/javascript" src="fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,800' rel='stylesheet' type='text/css' />
<!-- <link href='https://fonts.googleapis.com/css?family=Lora:400,400italic,700,700italic' rel='stylesheet' type='text/css' /> -->
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab:400,700" rel="stylesheet" type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css" integrity="sha256-Zd1icfZ72UBmsId/mUcagrmN7IN5Qkrvh75ICHIQVTk=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js" integrity="sha256-/BfiIkHlHoVihZdc6TFuj7MmJ0TWcWsMXkeDFwhi0zw=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/arduino.min.js" integrity="sha256-eWhKEgej7udQOehu4Pskiqt6+645UgTqGg7NntjfmPM=" crossorigin="anonymous"></script>
<script>hljs.initHighlightingOnLoad();</script>
<style type="text/css" media="all">
<!--
body {
background-color: #FFFFFF;
color: black;
font-family: 'Roboto Slab', serif;
font-size: 160%;
}
p {
margin-top: 1.5em;
}
h1 {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 800;
text-align: center;
color: #303030;
}
h2, h3 {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 800;
color: #303030;
margin-top: 1em;
}
#subtitle {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 800;
font-weight: 800;
text-align: center;
color: #303030;
font-size: 120%;
margin-bottom: 2em;
}
.gallery {
text-align: center;
margin-top: 2em;
margin-bottom: 2em;
}
.timestamp {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
text-align: right;
color: #888;
margin-top: 2em;
}
.fancybox-title {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
-->
</style>
<script type="text/javascript">
$(document).ready(function() {
$(".fancybox").fancybox({
openEffect : 'none',
closeEffect : 'none',
helpers : {
title : {
type : 'inside'
}
},
beforeShow : function() {
var alt = this.element.find('img').attr('alt');
this.inner.find('img').attr('alt', alt);
this.title = alt;
}
});
});
</script>
</head>
<body>
<div id="content" class="container">
<p class="timestamp">2018-10-09</p>
<h1>WiFi Doorbell</h1>
<p id="subtitle">A web-enabled WiFi doorbell</p>
<p class="gallery">
<a href="images/IMG_20181009_090614.jpg" class="fancybox"><img src="images/IMG_20181009_090614.jpg" height="320" alt="ESP32 with dfplayer and Adafruit 3&quot; speaker" /></a>
</p>
<p>When we moved in our house this year, one of the first things we missed was a doorbell. There was a bell button at the gate, but since the whole building had been gutted down to the concrete nobody knew where the cable ended. When I found a cut-off electric cable in the basement, I suspected that it could be the one leading to the button. With a little help of my beloved multimeter I could confirm that assumption.</p>
<p class="gallery">
<a href="images/IMG_20181009_204134.jpg" class="fancybox"><img src="images/IMG_20181009_204134.jpg" width="320" alt="A mysterious cut cable in the basement. Don't ask me what that black goey mass is, I have no idea." /></a>
</p>
<p>However, that led to the next problem: Not only was the cable cut 2 cm off the wall, there was also no way to extend it to the first floor. And a doorbell in the basement is pretty much useless. There are WiFi solutions available that allow to put the bell in a different room, but they are expensive and need a 220 V hookup. So we settled with a wireless doorbell for the time being but it turned out not to be completely reliable, pretty ugly in comparison to the existing button and I did not like the battery based solution. So I set out to revive the old doorbell with the help of a microcontroller. Or two.</p>
<h2 id="parts">Parts list</h2>
<p>The concept is easy: The first microcontroller detects the push on the button and sends a request to the second microcontroller via the home WiFi. That second microcontroller is connected to a speaker and plays a bell sound. Since I am pretty new to the Arduino world and wanted to keep things easy, I used Espressif ESP32 boards that have integrated WiFi. To make it even easier, I added a cheap MP3 player module.</p>
<ul>
<li>2&times; Espressif ESP32 development boards</li>
<li>dfplayer MP3 module with a micro SD card. I used a <a href="http://www.orangefreesounds.com/house-bell-sound-effect/">doorbell sound from orangefreesounds.com</a></li>
<li>Speaker: Adafruit 3&quot; (4&ohm; 3W ADA1314)</li>
<li>2&times; 1 k&ohm; resistor</li>
<li>2&times; 3.3 V LEDs to indicate operation (optional)</li>
<li>Pushbutton to trigger the bell from the sender (optional)</li>
<li>One 4&times;8 cm PCB and one 5&times;7 cm PCB</li>
</ul>
<h2>Assembly</h2>
<p>Not much to see here. Basically just throwing all the modules together. In the end I left out the LED on the speaker side. (It is still addressed in the code, though.)</p>
<p class="gallery">
<a href="images/Doorbell-Sender_electronics.png" class="fancybox" rel="collection01"><img src="images/Doorbell-Sender_electronics.png" width="480" alt="The sender wiring" /></a>
<a href="images/Doorbell-Speaker_electronics.png" class="fancybox" rel="collection01"><img src="images/Doorbell-Speaker_electronics.png" width="480" alt="The speaker wiring" /></a>
</p>
<h2>Case</h2>
<p>I designed the two cases in <a href="https://www.onshape.com">Onshape</a>, <a href="https://cad.onshape.com/documents/3d6277225c608df3d6fa088a/w/72843176600294ef1dbbfb59/e/1f1258242c4354183254fecf">one for the sender</a> and <a href="https://cad.onshape.com/documents/52e9d4e10e4bed6e46d8f76a/w/f57d76b7daf7074ebfd995fe/e/1db04c5107b512702799e3c3">one for the speaker unit</a>. The speaker case has a thin grid at the bottom and a little stand that is glued on in order to let the sound pass out.</p>
<p class="gallery">
<a href="images/onshape-00.png" class="fancybox" rel="collection02"><img src="images/onshape-00.png" width="480" alt="The sender case" /></a>
<a href="images/onshape-02.png" class="fancybox" rel="collection02"><img src="images/onshape-02.png" width="480" alt="The speaker case" /></a>
</p>
<h2>Results</h2>
<p class="gallery">
<!--a href="images/IMG_20181009_090614.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_090614.jpg" width="480" alt="Assembled speaker electronics" /></a-->
<a href="images/IMG_20181009_204240.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_204240.jpg" width="480" alt="The doorbell sender" /></a>
<a href="images/IMG_20181009_204213.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_204213.jpg" width="480" alt="The print of the sender case did not turn out good and I also soldered the button too low on the PCB. But since this is going into the basement anyway, I did not bother redoing it." /></a>
</p>
<p class="gallery">
<a href="images/IMG_20181009_090812.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_090812.jpg" width="480" alt="The speaker case with the bottom grid." /></a>
<a href="images/IMG_20181009_204821.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_204821.jpg" width="480" alt="Assembled speaker electronics." /></a>
</p>
<p class="gallery">
<a href="images/IMG_20181009_204645.jpg" class="fancybox" rel="collection03"><img src="images/IMG_20181009_204645.jpg" width="480" alt="The speaker, ready to be connected." /></a>
</p>
<h2>Code</h2>
<p>While the sender does not much more than blinking the LED slightly differently depending on what it is doing, the speaker unit offers a bit more: It starts up a tiny web server that provides the interface for the sender and can also be used directly via the browser. It allows also to set the speaker volume:</p>
<p class="gallery">
<a href="images/doorbell-webpage.png" class="fancybox"><img src="images/doorbell-webpage.png" alt="The web page of the doorbell speaker" /></a>
</p>
<h3>Sender Code</h3>
<pre><code class="arduino">/*
* Sources:
* https://www.arduino.cc/en/Tutorial/Button
* https://techtutorialsx.com/2017/05/19/esp32-http-get-requests/
*/
#include &lt;WiFi.h&gt;
#include &lt;HTTPClient.h&gt;
const char* ssid = "WiFi SSID";
const char* password = "WiFi password";
const char* bellUrl = "http://192.168.1.149/bell/on";
const int buttonPin = 21; // the number of the pushbutton pin
const int ledPin = 23; // the number of the LED pin
int buttonState = 0;
void setup() {
Serial.begin(115200);
btStop(); // turn off bluetooth
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT);
connectWifi();
}
void loop() {
if ((WiFi.status() == WL_CONNECTED)) {
buttonState = digitalRead(buttonPin);
delay(100);
if (buttonState == HIGH) {
digitalWrite(ledPin, HIGH);
HTTPClient http;
http.begin(bellUrl);
Serial.print("GET ");
Serial.println(bellUrl);
int httpCode = http.GET();
if (httpCode > 0) {
//String payload = http.getString();
Serial.println(httpCode);
//Serial.println(payload);
}
else {
Serial.println("Error on HTTP request");
}
http.end();
delay(200);
}
else {
digitalWrite(ledPin, LOW);
}
}
else {
Serial.println("WiFi not connected!");
digitalWrite(ledPin, HIGH);
delay(200);
digitalWrite(ledPin, LOW);
delay(50);
digitalWrite(ledPin, HIGH);
delay(200);
digitalWrite(ledPin, LOW);
connectWifi();
}
}
void connectWifi() {
boolean ledStatus = false;
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
ledStatus = !ledStatus;
digitalWrite(ledPin, ledStatus);
}
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}</code></pre>
<h3>Speaker Code</h3>
<pre><code class="arduino">/*
Sources:
https://www.youtube.com/watch?v=kq2RLz65_w0
https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299
https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_DFPlayer_full/ESP32_DFPlayer_full.ino
https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_DFPlayer_full/setup.png
https://randomnerdtutorials.com/esp32-web-server-arduino-ide/
*/
#include &lt;Arduino.h&gt;
#include "DFRobotDFPlayerMini.h"
#include &lt;WiFi.h&gt;
HardwareSerial hwSerial(1);
DFRobotDFPlayerMini dfPlayer;
int volume = 5;
const char* ssid = "WiFi SSID";
const char* password = "WiFi password";
WiFiServer server(80); // Set web server port number to 80
String header;
String ledState = "";
const int ledPin = 26;
unsigned long timestamp = 0;
void setup()
{
btStop(); // turn off bluetooth
hwSerial.begin(9600, SERIAL_8N1, 18, 19); // speed, type, TX, RX
Serial.begin(115200);
// WiFi & LED ==================================================================
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
delay(500);
dfPlayer.begin(hwSerial);
dfPlayer.setTimeOut(500);
dfPlayer.volume(volume); // Set volume value (0~30).
dfPlayer.EQ(DFPLAYER_EQ_NORMAL);
dfPlayer.outputDevice(DFPLAYER_DEVICE_SD);
ledState = "on";
digitalWrite(ledPin, HIGH);
timestamp = millis();
dfPlayer.play(1); // Play the first mp3
}
void loop() {
WiFiClient client = server.available(); // Listen for incoming clients
if (ledState == "on" && (millis() - timestamp) > 2000) {
ledState = "off";
digitalWrite(ledPin, LOW);
}
if (client) {
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
header += c;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// turns the GPIOs on and off
if (header.indexOf("GET /bell/on") >= 0) {
ledState = "on";
digitalWrite(ledPin, HIGH);
timestamp = millis();
dfPlayer.play(1); //Play the first mp3
}
else if (header.indexOf("GET /volume/") >= 0) { // yes, I know this is not RESTful
String str1 = header;
str1 = str1.substring(header.indexOf("GET /volume/") + 12);
volume = str1.substring(0, str1.indexOf(" ")).toInt();
if (volume < 0) {
volume = 0;
}
else if (volume > 30) {
volume = 30;
}
dfPlayer.volume(volume);
Serial.print("volume set to ");
Serial.println(volume);
}
// Display the HTML web page
client.println("&lt;!DOCTYPE html&gt;&lt;html&gt;");
client.println("&lt;head&gt;&lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;");
client.println("&lt;link rel=\"icon\" href=\"data:,\"&gt;");
client.println("&lt;title&gt;Doorbell&lt;/title&gt;");
client.println("&lt;style&gt;html { font-family: sans-serif; display: inline-block; margin: 0px auto; text-align: center; }");
client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
client.println(".button2 { background-color: #4CAF50; border: none; color: white; padding: 4px 10px; text-decoration: none; margin: 1px; cursor: pointer;}&lt;/style&gt;&lt;/head&gt;");
client.println("&lt;body&gt;&lt;h1&gt;Doorbell&lt;/h1&gt;");
client.println("&lt;p&gt;Volume: &lt;a href=\"/volume/" + String(volume-1) + "\"&gt;&lt;button class=\"button2\"&gt;&minus;&lt;/button&gt;&lt;/a&gt; " + String(volume) + " &lt;a href=\"/volume/" + String(volume+1) + "\"&gt;&lt;button class=\"button2\"&gt;&plus;&lt;/button&gt;&lt;/a&gt;&lt;/p&gt;");
client.println("&lt;p&gt;&lt;a href=\"/bell/on\"&gt;&lt;button class=\"button\"&gt;Ring&lt;/button&gt;&lt;/a&gt;&lt;/p&gt;");
client.println("&lt;p style='margin-top: 40px; font-size: 8px;'&gt;dfplayer status: " + printDetail(dfPlayer.readType(), dfPlayer.read()) + "&lt;/p&gt;");
client.println("&lt;p style='font-size: 8px;'&gt;LED - State " + ledState + "&lt;/p&gt;");
client.println("&lt;/body&gt;&lt;/html&gt;");
// The HTTP response ends with another blank line
client.println();
// Break out of the while loop
break;
} else { // if you got a newline, then clear currentLine
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
}
}
String printDetail(uint8_t type, int value){
switch (type) {
case TimeOut:
return "Time Out!";
break;
case WrongStack:
return "Stack Wrong!";
break;
case DFPlayerCardInserted:
return "Card Inserted!";
break;
case DFPlayerCardRemoved:
return "Card Removed!";
break;
case DFPlayerCardOnline:
return "Card Online!";
break;
case DFPlayerPlayFinished:
return "Play Finished!";
break;
case DFPlayerError:
switch (value) {
case Busy:
return "Error: Card not found";
break;
case Sleeping:
return "Error: Sleeping";
break;
case SerialWrongStack:
return "Error: Get Wrong Stack";
break;
case CheckSumNotMatch:
return "Error: Check Sum Not Match";
break;
case FileIndexOut:
return "Error: File Index Out of Bound";
break;
case FileMismatch:
return "Error: Cannot Find File";
break;
case Advertise:
return "Error: In Advertise";
break;
default:
break;
}
break;
default:
break;
}
}</code></pre>
</div>
</body>
</html>