使用ESP32和DFPlayer制作WiFi门铃

本文将介绍如何制作一款基于ESP32,并经由WiFi网络实现的门铃。关于ESP32的使用和接口定义,各位可阅读: ESP32专题的相关文章。

ESP32制作门铃
ESP32制作门铃

项目起因

今年新家装修的时候,我忽略了门铃的问题。原来门边上设置有一个门铃按钮,但由于原来预留的线都被混凝土掩盖了,没办法找到电缆头在哪里。于是纠结怎么安装一款门铃。到网上一搜,目前有WiFi门铃的解决方案,可以把电铃放在不同的房间,但价格相当昂贵,而且还需要220v的电源供电。于是我选择用无线门铃来解决,但结果证明无线门铃的运行并不是非常可靠,而且我不怎么喜欢基于电池的解决方案。于是,开始萌生了采用微控制器来解决这个问题,使旧门铃能复活过来。

工作原理

项目的思路是这样的:设置两片单片机,分别置于室内和室外,当第一个单片机检测到按钮上的按钮动作时,通过WiFi网络向第二个单片机发送请求。第二个单片机连接到喇叭,当它收到发送的请求时便播放预设的铃声。项目中我使用了集成WiFi的Espressif ESP32开发板。为了让项目更简单,我还添加了一个便宜的MP3播放器模块。

原件清单

  1. 两块 Espressif ESP32开发板 ;
  2. DFPlayer MP3模块和 SD卡;
  3. 4欧姆 3W的喇叭;
  4. 2个1K的电阻;
  5. 两个3.3V LED指示灯;
  6. 按钮开关;
  7. 面包板。

电路组装

这一部分比较简单,没什么值得注意的。基本上就是把所有的元件放在一起。最后,我把LED灯忘在扬声器那一侧了。(不过,最后代码中对其进行了处理。)

基于ESP32制作门铃之发送侧
基于ESP32制作门铃之发送侧

门铃的外壳是采用3D打印机打印的,分为两部分,室外部分和室内部分。附件是电路连接图和外壳的3D打印STL文件,需要可下载:

链接:https://pan.baidu.com/s/1iWZWZCnzvcEK_mDkdllG-g ,提取码:b8n4

 基于ESP32制作门铃之发送侧实物
基于ESP32制作门铃之发送侧实物
基于ESP32制作门铃之接收侧
基于ESP32制作门铃之接收侧
 基于ESP32制作门铃之接收侧实物
基于ESP32制作门铃之接收侧实物

代码

虽然发射端硬件上仅仅是稍微闪烁一下LED,功能比较单一,但接收单元提供了更多的功能,它可以启动一个小型web服务器,为发射端提供界面,也可以通过浏览器直接使用它。它还允许设置扬声器音量。

ESP32门铃的设置界面
ESP32门铃的设置界面

发送端代码

/*
 * Sources:
 * https://www.arduino.cc/en/Tutorial/Button
 * https://techtutorialsx.com/2017/05/19/esp32-http-get-requests/
 */

#include <WiFi.h>
#include <HTTPClient.h>
 
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());
}

接收端代码

/*
Sources:
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
ESP32 Web Server – Arduino IDE
*/ #include <Arduino.h> #include "DFRobotDFPlayerMini.h" #include <WiFi.h> 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); //Use softwareSerial to communicate with mp3 dfPlayer.setTimeOut(500); //Set serial communication time out 500ms 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("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); client.println("<link rel=\"icon\" href=\"data:,\">"); client.println("<style>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;}</style></head>"); client.println("<body><h1>LeWe Türklingel</h1>"); client.println("<p>Volume: <a href=\"/volume/" + String(volume-1) + "\"><button class=\"button2\">−</button></a> " + String(volume) + " <a href=\"/volume/" + String(volume+1) + "\"><button class=\"button2\">+</button></a></p>"); client.println("<p><a href=\"/bell/on\"><button class=\"button\">Klingeln</button></a></p>"); client.println("<p style='margin-top: 40px; font-size: 8px;'>dfplayer status: " + printDetail(dfPlayer.readType(), dfPlayer.read()) + "</p>"); client.println("<p style='font-size: 8px;'>LED - State " + ledState + "</p>"); client.println("</body></html>"); // 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; } }
标签: