在上一篇关于Esp32的文章中,我们通过Esp32的蓝牙系统控制了家里的灯光。结尾处提到,这套蓝牙方案似乎有点耗电,于是乎我采取了wifi这个方案解决。

大概讲一下原理和思路:通过服务器提交表单,更改数据库参数。另一边,单片机不停检测数据库特定参数数据,若发生改变,则执行相对应的命令。(我设置的参数 0 为关灯 1 为开灯)

Ⅰ.方案需求

  • 完整的控制设备(如果你还没有看上一期蓝牙版的教程,先去看完再继续哦)
  • 一个迷你服务器+数据库(只要是能任何时候都在浏览器能访问的网页即可)

Ⅱ.网络服务的搭建

如果你已经准备好一个能随时访问的网页和数据库,那么我们就开始配置吧!

一、在数据库中建立表

登录后台数据库,新建表名为 home_intellenges 的表,分别添加 id 和 value 字段(表名若要更改请根据后面的提示更改源码)

新建数据表
设置字段并添加如图的值(我这里有4个字段 大家不需要管 保证你的是 id + value 即可)

二、上传控制页面到你的网站

一共有三个页面,分别是:

  • index.html 远程控制器网页 用于控制灯的开和关
  • upload_db.php 用于将我们的操控数据上传到数据库
  • get_data.php 用于单片机请求并返回灯的开关状态数据

在你的后台新建一个文件夹,并将这三个文件上传上去(文件名不能改 除非自行改源码)

1.index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>岩石的智能家居</title>
    <style>
        body {
            background-color: rgb(173, 166, 166);
        }


        div.my_room {
            background-color: dodgerblue;
            text-align: center;
            position: absolute;
            margin: auto;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            width:300px;
            height:150px;
            border-radius: 100px;
            box-shadow: 0 0 100px 42px white;
        }

        .light1 {
            color: white;
        }

        .confirm_light1 {
            position: relative;
            top: 20px;
            width: 60px;
            height: 30px;
            border-radius: 190px;
        }

        
    </style>
</head>
<body>
    <form action="upload_db.php" method="post">
        <div class="my_room">
            <p class="light1">灯光控制1</p>
            <span class="light1">开:</span><input type="radio" name="house_light" value="1">
            <span class="light1">关:</span><input type="radio" name="house_light" value="0"><br>
            <button type="submit" class="confirm_light1">确定</button>
        </div>
    </form>
</body>
</html>

2.upload_db.php

<html>
<body>
    <?php
    $host = "localhost"; // 主机地址
    $db_admin = "sql_admin"; // 数据库账户名
    $db_name = "sql_name"; // 数据库名
    $db_passwd = "sql_pwd"; // 数据库登录密码
    // 数据表名 home_intellenges 若你创建的不是这个名称,请将所有的 home_intellenges 都换                       成你的


    // 定义变量并设置为空值
    $house_light = "";

    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        $house_light = server_input($_POST["house_light"]);
        // strcmp 二进制安全字符串比较
        if (strcmp($house_light, "1") == 0) {
            echo "开灯!";
            turn_on();
        } else {
            turn_off();
            echo "关灯!";
        }

    }

    // 开灯
    function turn_on() {
        $conn=mysqli_connect($host, $db_name, $db_passwd);
        if($conn){
            $select = mysqli_select_db($conn,$db_name);
            if($select) {
                $sql = "update home_intellenges set value = 1 where id = 1";
                $ok = mysqli_query($conn,$sql);
                if($ok) {
                    header("location:index.html");
                }
                else {
                    echo "数据库修改错误";
                }
            }
            else {
                echo "表文件链接错误";
            }
        }
        else {
            echo "无法连接数据库";
        }
    }

    // 关灯
    function turn_off() {
        $conn=mysqli_connect($host, $db_name, $db_passwd);
        if($conn){
            $select = mysqli_select_db($conn,$db_name);
            if($select) {
                $sql = "update home_intellenges set value = 0 where id = 1";
                $ok = mysqli_query($conn,$sql);
                if($ok) {
                    header("location:index.html");
                }
                else {
                    echo "数据库修改错误";
                }
            }
            else {
                echo "表文件链接错误";
            }
        }
        else {
            echo "无法连接数据库";
        }
    }

    function server_input($data) {
        $data = trim($data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data, ENT_QUOTES);
        
        return $data;
    }

    ?>


</body>
</html>

请根据文件开头的提示将数据录入完全,之后进入下一步。

3.get_data.php

<?php
$host = "localhost"; // 主机地址
$db_admin = "sql_admin"; // 数据库账户名
$db_name = "sql_name"; // 数据库名
$db_passwd = "sql_pwd"; // 数据库登录密码
// 数据表名 home_intellenges 若你创建的不是这个名称,请将所有的 home_intellenges 都换                       成你的

class CallBackData
{
    public $my_house_light1;
}


// 定义变量并设置为空值
$http_data = "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $http_data = server_input($_POST["house_light"]);
    // strcmp 二进制安全字符串比较
    // 接收 house_light: state
    if (strcmp($http_data, "state") == 0) {
        return_func();
    }
    else {
        echo "error code: 1";
    }


} else if ($_SERVER["REQUEST_METHOD"] == "GET") {
    $http_data = server_input($_GET["house_light"]);
    // strcmp 二进制安全字符串比较
    // 接收 house_light: state
    if (strcmp($http_data, "state") == 0) {
        return_func();
    }
    else {
        echo "error code: 2";
    }


} else {
    echo "error code: 3";
}

function return_func()
{
    $data = new CallBackData(); // 创建返回数据对象
    $conn=mysqli_connect($host, $db_name, $db_passwd);
    if($conn) {
        $select = mysqli_select_db($conn,$db_name);
        if($select) {
            $sql = "SELECT * FROM `home_intellenges` WHERE id = 1"; // 数据库执行语句 home_intellenges 为搜索的表名
            $ok = mysqli_query($conn, $sql);
            if($arr=mysqli_fetch_array($ok)) {
                $data->my_house_light1 = "".$arr['value']."";
                echo json_encode($data); // 返回JSON数据
            }
            else {
                echo "ERROR: cannot locate data";
            }
        }
    }
}


function server_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data, ENT_QUOTES);
    
    return $data;
}

?>

三、连接测试

通过输入 你的域名/文件路径/index.html 访问刚才上传上去的页面 查看是否成功

为了方便 用手机打开测试是没问题的

当你的页面出现上述画面的时候,恭喜你成功了!(请不要吐槽我的网页设计,只是随便弄的 想要弄得好看点可以自己修改 index.html 代码)

你可以通过提交开和关命令看看你数据库中的value值是否发生变化,如果是,那么恭喜你,可以进入下一步了。

Ⅲ.Esp32代码烧录

万事俱备,只欠东风!像上次那样,将我们的Esp32连接至电脑,开始烧录代码

废话不多说,直接上完整代码:

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// 请根据提示自行更改下面函数: WiFi.begin(); 里面的WIFI信息
// 请根据提示自行更改下面函数: http.begin(); 里面的内容
// 如果觉得响应的速度太慢,可以通过修改本页最后一个delay()函数的参数提高速度(默认5秒检测一次)

HTTPClient http;

 // 舵机通道 
int channel_PWM = 3;  

// 舵机频率,那么周期也就是1/50,也就是20ms ,PWM一共有16个通道,0-7位高速通道由80Mhz时钟驱动,后面8个为低速通道由1Mhz时钟驱动
int freq_PWM = 50;   

// PWM分辨率,取值为 0-20 之间  ,这里填写为10,那么后面的ledcWrite 这个里面填写的pwm值就在 0 - 2的10次方 之间 也就是 0-1024 ,如果是要求不高的东西你可以直接拿1000去算了
int resolution_PWM = 10;   

// 绑定的IO,在下面的绑定函数里面会用到,绑定之后这个IO就会变成我们PWM的输出口
const int PWM_Pin = 4;  //指定pwm绑定到这个io上输出

#define PMW_EN 0
const int Motor_PWM_PinB = 4;

int interruptCounter = 0;
hw_timer_t *timer = NULL;



void IRAM_ATTR TimerEvent()
{
    interruptCounter++;
    // Serial.println(interruptCounter++);
    if (interruptCounter > 5)
    {
        interruptCounter = 1;
    }
    // Serial.println(ledcRead(5));
}


void WiFi_Connect()
{
	WiFi.begin("wifi_account", "password"); // 在这里输入你区分大小写的wifi名称和密码
	while (WiFi.status() != WL_CONNECTED)
	{ //这里是阻塞程序,直到连接成功
		delay(300);
		Serial.print(".");
	}
}

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

    WiFi.mode(WIFI_STA); //设置为STA模式
    WiFi.disconnect();   //断开当前可能的连接
    delay(1000);


    // 初始化电机
    ledcSetup(channel_PWM, freq_PWM, resolution_PWM); // 设置舵机通道


    timer = timerBegin(0, 80, true);
    timerAttachInterrupt(timer, &TimerEvent, true);
    timerAlarmWrite(timer, 1000000, true);
    timerAlarmEnable(timer); //	使能定时器

    pinMode(2, OUTPUT);
    digitalWrite(2, HIGH);

    WiFi_Connect(); // 尝试连接wifi

    // 灯灭表示wifi连接成功
    digitalWrite(2, LOW);


}

int flag = 1; // 记录灯的状态

void loop()
{
    if (WiFi.status() == WL_CONNECTED)
    {
      http.begin("在这里输入get_data.php的完整路径之后再后面加上?house_light=state"); // 例如 http://littlerook.top/light/get_data.php?house_light=state

      int httpCode = http.GET();
      if (httpCode == HTTP_CODE_OK) {

        String pageData = http.getString(); // 获取页面
        StaticJsonDocument<200> doc;
        deserializeJson(doc, pageData); // 解析json文件包
        int callback = doc["my_house_light1"];

        // Serial.println(callback); // 获取内容
        if (callback == 1 && callback != flag) {
                Serial.println("LIGHT_ON");
                delay(100);
                ledcAttachPin(PWM_Pin, channel_PWM);  //将 LEDC 通道绑定到指定 IO 口上以实现输出
                ledcWrite(channel_PWM, 101); //86
                delay(300);
                ledcWrite(channel_PWM, 77);
                delay(100);
                ledcDetachPin(PWM_Pin);  //这个是解除IO口的pwm输出功能模式

                flag = 1;
        }
        if (callback == 0 && callback != flag) {
                Serial.println("LIGHT_OFF");
                delay(100);
                ledcAttachPin(PWM_Pin, channel_PWM);  //将 LEDC 通道绑定到指定 IO 口上以实现输出
                ledcWrite(channel_PWM, 57);
                delay(300);
                ledcWrite(channel_PWM, 77);
                delay(100);
                ledcDetachPin(PWM_Pin);  //这个是解除IO口的pwm输出功能模式

                flag = 0;
        }


      }
      else
      {
          digitalWrite(2, HIGH);

          WiFi_Connect(); // 尝试连接wifi

          // 灯灭表示wifi连接成功
          digitalWrite(2, LOW);
      }

      http.end();
    }


    delay(5000); // 每5秒运行一次
    
}

请按照代码行前面的提示修改特定位置的参数 上传后连好电线,理论上你的Esp32-Wifi控制版已经完成了!接下来只要你家WIFI不断,你在任何地方都能控制你家的灯!

Ⅳ.小结

细心的小伙伴已经发现了,这个方案还是有些许问题,比如当我们手动关闭开关的时候,下次还需要先往反方向打一次,再开灯,这样会浪费一点时间,我的解决办法是将检测延迟降低,降低等待成本,除此之外,暂时想不到其他更好的方法。

另外如果想增加多几个控制的灯,这就需要大家按照源码增加代码就可以了,注释也写得很明白了,如果有不明白的可以在评论区问我哦,我看到都会回复的。

但是总的来讲,这个方案已经比较完善,大家在享受科技带来的进步的时候,请不要得意地将控制地址分享给你的“好”基友!否则半夜灯闪个不停就能怪我了,总不能增加个登录验证吧!