Arduino爱好者

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 204|回复: 0

魔改遥控器,ESP8266+继电器通过苹果Siri控制打开家里的门

[复制链接]
发表于 2023-1-23 05:51 | 显示全部楼层 |阅读模式
本帖最后由 cih1996 于 2023-1-23 05:56 编辑

如果想脱离局域网(即不再同一个Wi-Fi)


那么就需要购买一个服务器作为中间件传输指令了。

这里我们必须为3部分讲解:
第1部分:硬件的对接
第2部分:服务器代码的逻辑设计
第3部分:ESP8266代码的实现
第4部分:Siri快捷指令设计

第1部分:硬件的对接
4.jpg
首先供电方法是用的双USB输出5V的充电宝; 分别给ESP8266和1路继电器进行供电。
无线遥控器是使用的R433X震晶模块,这里我直接选择对它的遥控按钮下手。
将开门的2个引脚点(按下时通电开门)用电线焊接在一起并接入继电器的COM和ON端,这样就可以通过继电器的闭合实现了模拟按下操作。
ESP8266这边接入的是D1引脚,到时候通过代码控制D1输出高电平,触发继电器闭合。

第2部分:服务器代码的逻辑设计
如果我们脱离局域网,不直接访问ESP8266模块进行控制D1引脚的话,就需要借助服务器。
1、首先需要设计数据库,存放设备的标志符,也可以说是唯一ID号(自己定义,方便多个设备的时候进行区别)。
2、然后设计出一个API接口,用于给Siri执行,这个接口的作用就是给设备做上开门的指令,存放到数据库里
3、在设计出一个API接口,用于给ESP8266模块读取开门指令(如果Siri执行了就有指令,如果没执行的话就没有)
4、基本上整个操作就这样了,Siri访问我们的接口把指令放到数据库,ESP8266访问我们的数据库读取到指令进行反应。

数据库我设计了2个表,1个是设备列表,1个是指令分配表

设备表:esp_device_list
4个主要字段:
1、device_name 设备的名字,方便我们观看。
2、device_id 设备的唯一ID,用于区分设备。
3、invo_count 设备执行指令的次数,便于统计。
4、heart_time 设备最后一次访问服务器的时间,用于方便我们判断是否还在线。
5.png

指令分配表:esp_device_command
6.png
1、device_id 设备的唯一ID
2、command 设备的指令(自己定义)
3、is_read 设备是否已经获取了指令(如果ESP8266读取到了指令,则改成1,避免二次读取)
4、callback_state 设备回调指令状态(某些特殊情况,我们需要知道ESP8266有没有成功的执行指令,如果成功了,可以通过改变这里的字段进行通知)
5、create_time 指令的创建时间(便于做日记查询)

下面分别是指令下发和指令读取的PHP代码:
以下是提供给Siri下发指令的API接口
7.png
以下是提供给ESP8266读取的API接口
8.png

另外为还扩展了一下额外的接口,方便进行后续的优化,简单设计了后台页面,便于可视化操作。
9.png


第3部分:ESP8266代码的实现
先来看看ESP8266的代码,用的是wifiinfo开发版:
  1. #include <ESP8266WiFi.h>
  2. #include <WiFiClient.h>
  3. #include <ESP8266WebServer.h>
  4. #include <ESP8266mDNS.h>
  5. #include <ESP8266HTTPClient.h>
  6. #include <ArduinoJson.h>


  7. #ifndef STASSID
  8. #define STASSID "Wi-Fi名字"
  9. #define STAPSK  "Wi-Fi密码"

  10. #endif

  11. const char* ssid = STASSID;
  12. const char* password = STAPSK;
  13. String device_id = "A001";
  14. //saved wifi ssid and password file path
  15. String file_wifi_config = "/wifiInfo.config";
  16. DynamicJsonDocument JSON_Buffer(1*1024);
  17. ESP8266WebServer server(80);

  18. const int led = 13;

  19. void initWifiConnect(){
  20.   File dataFile = SPIFFS.open(file_wifi_config,"r");
  21.   String dataStr = dataFile.readString();
  22.   dataFile.close();
  23.   Serial.println(dataStr);

  24.   WiFi.mode(WIFI_STA);
  25.   WiFi.begin(ssid, password);
  26.   Serial.println("");

  27.   // Wait for connection
  28.   while (WiFi.status() != WL_CONNECTED) {
  29.     delay(500);
  30.     Serial.print(".");
  31.   }
  32.   Serial.println("");
  33.   Serial.print("Connected to ");
  34.   Serial.println(ssid);
  35.   Serial.print("IP address: ");
  36.   Serial.println(WiFi.localIP());
  37. }

  38. //Show Wifi Config Page
  39. void handleWifiConfig(){
  40.   String outHtml = "<!DOCTYPE html><html><head>        <meta charset="utf-8">        <meta name="viewport" content="width=device-width, initial-scale=1">        <title>配置无线网络</title></head><body>        <div style="margin: 0;padding: 1rem;color: #ffffff;background-color: rgb(10,10,10);">                请配置无线网络信息        </div>        <form action="setWifi">                <p>无线网络:<input type="text" name="ssid" placeholder="请输入Wi-Fi名称"></p>                <p>无线密码:<input type="text" name="password" placeholder="请输入Wi-Fi密码"></p>                <p><input type="submit" value="提交链接" name=""></p>        </form></body></html>";
  41.   server.send(200, "text/html", outHtml);
  42. }

  43. //Recv User Input Wifi Config
  44. void handleWifiSet(){
  45.   String ssid = server.arg("ssid");
  46.   String password = server.arg("password");
  47.   server.send(200, "text/plain", "connecting...");
  48.   WiFi.begin(ssid, password);
  49.   Serial.println("");
  50.   Serial.print("Connected to ");
  51.   Serial.println(ssid);
  52.   Serial.print("IP address: ");
  53.   Serial.println(WiFi.localIP());
  54.   File dataFile = SPIFFS.open(file_wifi_config,"w");
  55.   dataFile.println(ssid);
  56.   dataFile.println(password);
  57.   dataFile.close();
  58. }

  59. void handleNotFound() {
  60.   digitalWrite(led, 1);
  61.   String message = "File Not Found\n\n";
  62.   message += "URI: ";
  63.   message += server.uri();
  64.   message += "\nMethod: ";
  65.   message += (server.method() == HTTP_GET) ? "GET" : "POST";
  66.   message += "\nArguments: ";
  67.   message += server.args();
  68.   message += "\n";
  69.   for (uint8_t i = 0; i < server.args(); i++) {
  70.     message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  71.   }
  72.   server.send(404, "text/plain", message);
  73.   digitalWrite(led, 0);
  74. }

  75. void setup(void) {
  76.   pinMode(led, OUTPUT);
  77.   pinMode(D1, OUTPUT);
  78.   digitalWrite(led, 0);
  79.   Serial.begin(115200);
  80.   initWifiConnect();

  81.   if (MDNS.begin("esp8266")) {
  82.     Serial.println("MDNS responder started");
  83.   }

  84.   server.on("/", handleWifiConfig);
  85.   server.on("/setWifi", handleWifiSet);
  86.   server.on("/open", []() {
  87.     digitalWrite(D1, 1);
  88.     delay(1000);
  89.     digitalWrite(D1, 0);
  90.     server.send(200, "text/plain", "open success~!");
  91.   });

  92.   
  93.   server.onNotFound(handleNotFound);

  94.   /////////////////////////////////////////////////////////
  95.   // Hook examples

  96.   server.addHook([](const String & method, const String & url, WiFiClient * client, ESP8266WebServer::ContentTypeFunction contentType) {
  97.     (void)method;      // GET, PUT, ...
  98.     (void)url;         // example: /root/myfile.html
  99.     (void)client;      // the webserver tcp client connection
  100.     (void)contentType; // contentType(".html") => "text/html"
  101.     Serial.printf("A useless web hook has passed\n");
  102.     Serial.printf("(this hook is in 0x%08x area (401x=IRAM 402x=FLASH))\n", esp_get_program_counter());
  103.     return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
  104.   });

  105.   server.addHook([](const String&, const String & url, WiFiClient*, ESP8266WebServer::ContentTypeFunction) {
  106.     if (url.startsWith("/fail")) {
  107.       Serial.printf("An always failing web hook has been triggered\n");
  108.       return ESP8266WebServer::CLIENT_MUST_STOP;
  109.     }
  110.     return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
  111.   });

  112.   server.addHook([](const String&, const String & url, WiFiClient * client, ESP8266WebServer::ContentTypeFunction) {
  113.     if (url.startsWith("/dump")) {
  114.       Serial.printf("The dumper web hook is on the run\n");

  115.       // Here the request is not interpreted, so we cannot for sure
  116.       // swallow the exact amount matching the full request+content,
  117.       // hence the tcp connection cannot be handled anymore by the
  118.       // webserver.

  119. #ifdef STREAMSEND_API
  120.       // we are lucky
  121.       client->sendAll(Serial, 500);
  122. #else
  123.       auto last = millis();
  124.       while ((millis() - last) < 500) {
  125.         char buf[32];
  126.         size_t len = client->read((uint8_t*)buf, sizeof(buf));
  127.         if (len > 0) {
  128.           Serial.printf("(<%d> chars)", (int)len);
  129.           Serial.write(buf, len);
  130.           last = millis();
  131.         }
  132.       }
  133. #endif
  134.       // Two choices: return MUST STOP and webserver will close it
  135.       //                       (we already have the example with '/fail' hook)
  136.       // or                  IS GIVEN and webserver will forget it
  137.       // trying with IS GIVEN and storing it on a dumb WiFiClient.
  138.       // check the client connection: it should not immediately be closed
  139.       // (make another '/dump' one to close the first)
  140.       Serial.printf("\nTelling server to forget this connection\n");
  141.       static WiFiClient forgetme = *client; // stop previous one if present and transfer client refcounter
  142.       return ESP8266WebServer::CLIENT_IS_GIVEN;
  143.     }
  144.     return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
  145.   });

  146.   // Hook examples
  147.   /////////////////////////////////////////////////////////

  148.   server.begin();
  149.   Serial.println("HTTP server started");
  150. }

  151. JsonObject root;                                      /* 定义一个JSON对象的根节点root 用于获取将JSON字符串经过显示类型转换为JSON对象的报文主体 */
  152. int JsonCode;
  153. const char* JsonCommand = NULL;
  154. void loop(void) {
  155.   if ((WiFi.status() == WL_CONNECTED)) {
  156.     WiFiClient client;
  157.     HTTPClient http;
  158.     http.begin(client, "http://www.abc.com/api/device/heartbeat?device_id=" + device_id );
  159.     http.addHeader("Content-Type", "application/json");
  160.     int httpCode = http.GET();
  161.     if(httpCode == HTTP_CODE_OK){
  162.       const String& payload = http.getString();
  163.       //decode Json String
  164.       DeserializationError error = deserializeJson(JSON_Buffer, payload);
  165.       if(error){
  166.         Serial.println("deserializeJson JSON_Buffer is ERROR!!!");
  167.         return ;
  168.       }else{
  169.         root = JSON_Buffer.as<JsonObject>();

  170.         //Print Json Array
  171.         //serializeJsonPretty(JSON_Buffer,Serial);
  172.         //Get Json Key-Value
  173.         JsonCode = JSON_Buffer["code"];
  174.         Serial.print("code --> ");
  175.         Serial.println(JsonCode);
  176.         if(JsonCode == 0){
  177.           JsonCommand = JSON_Buffer["command"];
  178.           //execl open
  179.           Serial.print("command --> ");
  180.           Serial.println(JsonCommand);
  181.           if(String(JsonCommand) == "open"){
  182.             Serial.println("execl open");
  183.             digitalWrite(D1, 1);
  184.             delay(1000);
  185.             digitalWrite(D1, 0);
  186.           }
  187.         }

  188.       }

  189.       Serial.println("received payload:\n<<");
  190.    
  191.       
  192.       Serial.println(">>");      
  193.     }
  194.     http.end();
  195.     delay(2000);
  196.   }
  197.   server.handleClient();
  198.   MDNS.update();
  199. }
复制代码


代码是引用自WifiServer的例子进行修改,所以大可不必直接复制粘贴,避免报错。
因为里面引用到了ArduinoJson库,需要在库管理里面安装。

这里重点说一下代码流程:
先设置好esp8266连接到你家里的Wi-Fi网络。
1.png
然后在loop处循环访问你的服务器接口,也就是刚刚设计的:/api/device/heartbeat 接口,用于返回指令的结果。
10.png
command就是由我们后面通过Siri自定义设置的,当我们给command设置为"open"时,以下图片所示的代码,读取到了open,则给D1输出电压,让继电器触发1秒时间在断开,因为是门,只需要模拟人工按一下就可以恢复了。
2.png


第4部分:Siri快捷指令设计
Siri的快捷指令,其实可以1行代码就可以实现。
我们添加获取URL内容指令
11.png
URL就输入 http://你的服务器IP/api/device/heartbeat?device_id=A001&command=open
最后把指令的名字改成"开门"。
12.png
这个时候你只需要说“嘿Siri,开门” 就能成功触发这个API接口。

当然我为了更智能,我还加了一个API接口,记得刚刚我们数据库有一个heart_time字段,这样我就可以先判断这个设备是不是还在线。如果不在线了就语音播报提醒,如果在线在创建指令。

更多技术交流群:251129682

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino爱好者

GMT+8, 2023-1-28 22:36 , Processed in 0.075192 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表