基于ESP32的远程开关灯控制(ESP32+舵机+Android+物联网云平台) ...

打印 上一主题 下一主题

主题 921|帖子 921|积分 2763

前言:出租屋、宿舍网上关灯标题,计划弄一个智能开关以及带一点安防本领,如检测有人进入之类的,同时也是回首一下以前把握的知识。
质料+环境预备

本身这个东西不难做,网上也有了很多的实现案例,没什么技能难度,但是有一定的实用效果。
本计划分为三个部分:ESP32(硬件)、Android、阿里云
物理质料

esp32开发板:控制焦点、网络连接
舵机(SG90-180°):用于控制拨动开关实现开关灯
杜邦线:连接模块大概进行跳线接线
供电部分:考虑供电部分的话,需要锂电池、充放电模块、电烙铁等焊接工具。
扩展:超声波模块,用于检测是否有人进入,大概实现夜间回家自动开灯等。
软件环境

esp32 arduino开发环境,需要设置首选项,json、下载esp32开发板的包(耗时长,大概失败),网上有一些离线安装的方法教程可以参考。
esp32 esp-idf框架,这个也是一个esp32的开发环境,可以在vscode上集成,听说用起来很方便,和arduino二选一即可,我现在用的arduino,配置好了esp-idf框架还未利用(感觉编译要太长的时间了。。。。)
android studio:用于开发android软件,方便调试,自己开发具有更高的扩展性,方便连接到云平台,这个需要一定的底子,最好以前弄过,否则搭环境下软件也挺费时间。
阿里云:重要也是为了实现无距离限定控制,这就是上云的好处,此外也可以实现,如果不在家,有人进了房子配合超声波大概激光实现提醒入侵检测的功能。此外大概也可以通过其他的物联网平台和对应的app实现远程控制,比如电灯科技还可以参加米家,实现小爱语言远程控制,但是相对而言可扩展性小。
物联网平台配置(MQTT)

起首要先容云平台是由于其他两个部分都需要连接到云平台,以是这里的配置有限,另外就是先容mqtt协议进行先容,方便理解后续代码的含义。
MQTT

八股文我就不说了,直接说大概实现的原理和通讯的过程。
条件:设备联网可以利用mqtt协议、云平台(中心做消息流转)
云平台起首要创建产品、创建设备,产品可以理解为一个类,设备是实现的子类,这些子类可以有一些共同的特性,这是产品里面定义的。一个设备就是对应现实的一个物理设备,创建好设备后,会有对应的暗码、设备名称、ID之类的一些标志,用来将你的设备连接到云平台。这应该就是mqtt协议中规定好的。
MQTT是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议。发布就是指发布消息,你发布消息给云平台,大概云平台发布给你。订阅就是你订阅某一个主题(topic),这样云平台就可以通过这个主题来给你发布消息。
消息流转:设备和云平台可以通过订阅和发布的方式进行消息传递,但是设备和设备之间无法直接进行通讯,需要通过云平台来进行中心消息转发。这个是通过云平台实现的,云平台设置即可,无需代码实现。
阿里云平台配置

本次计划是通过阿里云物联网平台实现相应的功能。
阿里云物联网平台
注册登录,阿里云物联网平台提供免费的公共实例可以试用。
创建产品


添加设备

然后为对应产品添加设备,需要添加两个设备,一个是esp32的,一个是手机app的。

创建好设备后可以查看设备,里面有相干的连接信息。

这些信息都需要在后续中利用,届时进行查看复制进入对应代码区即可。
自定义topic

注:产品可以设置物理模型,设置一些共有属性,这样物联网平台也可以进行控制和查看对应的属性,设置好物理模型后,会有专门对应的topic进行发送相干属性的消息,但同样也包含了一些其他无关信息,为了方便我可以可以自定义topic,这些操作都需要在产品中区定义实现。
定义topic类可以自己命名topic,并选择范例,发布还是订阅,大概两个都选,订阅的话表示这个主题是用来订阅的,也就是设备订阅了这个主题后,可以从云台中看到,并通过这个订阅发布消息。
发布的主题指的就是设备用来发布消息的,将消息传递至云平台的topic。
如果不需要向云平台发布消息,可以不进行定义,产品中需要手机向云平台发布开灯消息,以是需要进行定义。
我定义了两个主题一个是舵机的,一个是超声波,重要是想实现两个功能,都是基于发布和订阅的,这有利有弊,比较方便吧。

区别可以从下面的例子中看出来
我的设备定义了三个topic,分别是物理模型,自带的一个属性设置的topic,两个自定义topic

产品物理模型自带的topic

可以看到自带的主题的操作权限,只有订阅,也就是设备用来订阅,无法进行发布,这样在设备列表中订阅的topic中无法实现发布消息功能,而另外两个自定义主题由于设置了发布和订阅,以是订阅后,同时也可以进行消息发布。
本节的重要目标创建产品,创建对应的自定义主题,创建两个设备获取连接信息,用于设备进行mqtt通讯连接。
esp32配置

本节重要先容esp32的配置和物理设备的接线。
接线


起首看接线,由于用到超声波,需要的也是5v的电压供电,但是开发板只有一个5V口,此外还要和充放电模块接线,以是这里肯定需要将几根线剪开连接起来,并用电工胶带绑好。
如果你只有一个就直接用杜邦线就可以,但是考虑供电也需要接线,大概找一个带usb口的充放电模块,这样直接用usb供电给开发板也可以。
如果自行计划PCB天然就会更方便,开端测试时就没有打板。
代码

现阶段,代码非常简单,由于有很多的封装库实现,以是控制代码调用函数就可以,设置都不用去管什么pwm控制,设置占空比什么什么的,非常方便,esp32和esp8266这些arduino的都是一样的,确实是加快了开发进程。
控制代码非常简单,重要就是上云,连接到阿里云了。
以下就是完整代码
重要包含功能:
ESP32连接wifi
初始化舵机(控制信号线),控制舵机(设置角度)
超声波测距,并自动开关灯(根据时间) getDistance()
上报(到云)人员经过超声波检测的时间,可以进行记录查看到有人进出房间的时间
(超声波这部分代码注销了,缘故原由有很多,一个是未便调试,另外超声波的布置位置很不方便,也容易导致误触,线长的标题,另外就是房间的标题,未便部署,激光模块效果更优)
阿里云mqtt通讯连接的代码,回调处置惩罚代码,就是吸收到云平台的订阅消息时做出的逻辑处置惩罚
aliyun_callback()
还有连接和订阅操作,这些都比较简单调用函数即可,这里的回调中实现了两种方式,一种是基于产品的物理模型提供的topic进行订阅和操作,一个是自定义的进行操作,自定义的topic相对比较简单。
库安装

对应的头文件需要安装对应的库,ESP32Servo、NTPClient、ArduinoJson、PubSubClient,末了这个需要注意。PubSubClient需要对源码的参数进行修改,否则会连接失败。
找到你库安装的路径,c:\Users****\Documents\Arduino


修改这两个宏定义即可,修改为1024和60。
  1. // MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize().
  2. #ifndef MQTT_MAX_PACKET_SIZE
  3. #define MQTT_MAX_PACKET_SIZE 1024
  4. #endif
  5. // MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive()
  6. #ifndef MQTT_KEEPALIVE
  7. #define MQTT_KEEPALIVE 60
  8. #endif
复制代码
MQTT_MAX_PACKET_SIZE表示的是客户端能够处置惩罚的最大数据包巨细(字节),MQTT_KEEPALIVE表示的是定义客户端与服务器之间的心跳间隔(秒),用于维持长连接。
当然也可以不修改,直接在代码中进行定义覆盖,有两种方式,一种是头文件前进行定义
  1. #define MQTT_MAX_PACKET_SIZE 1024
  2. #define MQTT_KEEPALIVE 60
  3. #include <PubSubClient.h>
复制代码
这里编译的时候头文件时通过#ifndef检查该常量已经定义过了就不会再定义,以是可以覆盖文件中的宏定义。
大概直接利用动态函数修改,可以在在程序中动态调整
  1.   client.setBufferSize(1024);  // 运行时覆盖为1KB
  2.   client.setKeepAlive(60);     // 覆盖默认60秒
复制代码
以下是完整代码
  1. #include <WiFi.h>
  2. #include <ESP32Servo.h>
  3. //如果不想修改源文件,就直接在代码中取消下面两行注释即可
  4. //#define MQTT_MAX_PACKET_SIZE 1024
  5. //#define MQTT_KEEPALIVE 60
  6. #include <PubSubClient.h>
  7. #include <ArduinoJson.h>
  8. #include <NTPClient.h>
  9. #include <WiFiUdp.h>
  10. #include <time.h> // 用于时间处理
  11. // 定义 NTP 客户端
  12. WiFiUDP ntpUDP;
  13. NTPClient timeClient(ntpUDP, "pool.ntp.org", 8 * 3600, 60000);
  14. // 使用 pool.ntp.org 服务器,时区偏移为 8 小时(东八区)
  15. // WiFi配置
  16. const char* ssid = "*****";//wifi名称
  17. const char* password = "******";//wifi密码
  18. // 阿里云物联网平台配置
  19. const char* mqtt_server = ""; // 替换为你的阿里云IoT endpoint
  20. const char* mqtt_username = ""; // 替换为你的设备名称和产品Key
  21. const char* mqtt_password = ""; // 替换为你的设备密钥
  22. const char* mqtt_client_id = ""; // 替换为你的客户端ID
  23. // esp32_601
  24. // MQTT主题
  25. const char* servo_topic = ""; // 替换为你的舵机控制主题
  26. const char* person_detected_topic = ""; // 替换为你的人员检测主题
  27. WiFiClient espClient;               //创建网络连接客户端
  28. PubSubClient mqttClient(espClient); //通过网络客户端连接创建mqtt连接客户端
  29. // 舵机引脚
  30. const int servoPin = 27;
  31. Servo myServo;
  32. //调试后的固定开关等角度值
  33. const int light_off = 112;
  34. const int light_on = 72;
  35. // 这两个值需要自己进行调试,此外我设置的是90°的时候舵机是横向的
  36. // 超声波引脚
  37. const int trigPin = 25;
  38. const int echoPin = 26;
  39. // led引脚
  40. const int led0Pin = 2;
  41. //板载led可以用来调试观察现象
  42. // 初始化LED
  43. void setupLED() {
  44.   pinMode(led0Pin, OUTPUT);
  45.   digitalWrite(led0Pin, LOW);  // 初始状态为关闭
  46. }
  47. // 初始化舵机
  48. void setupServo() {
  49.   myServo.attach(servoPin); // 将舵机连接到指定引脚
  50.   myServo.write(90);        // 初始角度设为90度
  51. }
  52. // 初始化超声波传感器
  53. void setupUltrasonic() {
  54.   pinMode(trigPin, OUTPUT);
  55.   pinMode(echoPin, INPUT);
  56. }
  57. // 超声波测距函数
  58. float getDistance() {
  59.   digitalWrite(trigPin, LOW);
  60.   delayMicroseconds(2);
  61.   digitalWrite(trigPin, HIGH);
  62.   delayMicroseconds(10);
  63.   digitalWrite(trigPin, LOW);
  64.   long duration = pulseIn(echoPin, HIGH); // 读取回波时间
  65.   float distance = duration * 0.034 / 2;  // 计算距离(单位:厘米)
  66.   return distance;
  67. }
  68. //初始化连接wifi
  69. void setup_wifi() {
  70.   delay(10);
  71.   Serial.println();
  72.   Serial.print("Connecting to ");
  73.   Serial.println(ssid);
  74.   WiFi.begin(ssid, password);
  75.   while (WiFi.status() != WL_CONNECTED) {
  76.     delay(500);
  77.     Serial.print(".");
  78.   }
  79.   Serial.println("");
  80.   Serial.println("WiFi connected");
  81.   Serial.print("IP address: ");
  82.   Serial.println(WiFi.localIP());
  83. }
  84. void aliyun_callback(char* topic, byte* payload, unsigned int length) {
  85.   Serial.print("Message arrived [");
  86.   Serial.print(topic);
  87.   Serial.print("] ");
  88.   for (int i = 0; i < length; i++) {
  89.     Serial.print((char)payload[i]);
  90.   }
  91.   Serial.println();
  92.   //上面是打印接收到的订阅信息,调试用
  93.   // 处理舵机控制消息 --- 物理模型版本
  94.   // if(strcmp(topic, "/sys/${}/${设备名}/thing/service/property/set")== 0){
  95.   //   // 将 payload 转换为字符串
  96.   //   char message[length + 1];
  97.   //   memcpy(message, payload, length);
  98.   //   message[length] = '\0'; // 添加字符串结束符
  99.   //   Serial.println(message);
  100.   //   // 解析 JSON 数据
  101.   //   StaticJsonDocument<200> doc; // 根据 JSON 数据大小调整缓冲区大小
  102.   //   DeserializationError error = deserializeJson(doc, message);
  103.   //   // 检查解析是否成功
  104.   //   if (error) {
  105.   //       Serial.print("Failed to parse JSON: ");
  106.   //       Serial.println(error.c_str());
  107.   //       return;
  108.   //   }
  109.   //   // {"method":"thing.service.property.set","id":"1107300794","params":{"servo":1},"version":"1.0.0"}   这是物理模型消息模板,真正需要的数据就是{"servo":1}
  110.   //   // 提取 JSON 字段
  111.   //   int servo_angle = doc["params"]["servo"]; // 获取 command 字段
  112.   //   // int angle = doc["angle"];             // 获取 angle 字段
  113.   //   // int duration = doc["duration"];       // 获取 duration 字段
  114.   //   myServo.write(servo_angle);//设置角度
  115.   // }
  116.   // 处理舵机控制消息  自定义模型,自己定义主题topic和解析,比较简单方便,不需要多余的处理
  117.   if (strcmp(topic, servo_topic) == 0) {
  118.     //获取消息  获取设置值
  119.         // 将字节数组转换为字符串
  120.     char payloadStr[length + 1];  // 加1是为了加上字符串结束符 '\0'
  121.     memcpy(payloadStr, payload, length);
  122.     payloadStr[length] = '\0';  // 确保字符串结束
  123.     // 假设 payloadStr 是一个整数的文本格式  消息体内容只有舵机的设置参数为纯数字
  124.     int intValue = atoi(payloadStr);
  125.     Serial.print("Received integer value: ");
  126.     Serial.println(intValue);
  127.     if(intValue>=0 && intValue<=180){
  128.       myServo.write(intValue); // 控制舵机转动到90度
  129.       delay(500);
  130.       myServo.write(90);//设置完后自动调整为90,这样不会卡住手动拨动灯开关
  131.     }
  132.       
  133.   }
  134. }
  135. void reconnect() {
  136.   while (!mqttClient.connected()) {
  137.     Serial.print("Attempting MQTT connection...");
  138.     if (mqttClient.connect(mqtt_client_id, mqtt_username, mqtt_password)) {
  139.       Serial.println("connected");
  140.       mqttClient.subscribe(servo_topic);
  141.       mqttClient.subscribe(person_detected_topic);//连接成功后订阅主题,这样才能接收到消息
  142.     } else {
  143.       Serial.print("failed, rc=");
  144.       Serial.print(mqttClient.state());
  145.       Serial.println(" try again in 5 seconds");
  146.       delay(5000);
  147.     }
  148.   }
  149. }
  150. void setup() {
  151.   Serial.begin(115200);
  152.   setupLED();
  153.   setupServo();          // 初始化舵机
  154.   setupUltrasonic();     // 初始化超声波传感器
  155.   setup_wifi();         //初始化连接wifi
  156.   // 初始化 NTP 客户端
  157.   timeClient.begin();
  158.   timeClient.update(); // 获取时间
  159.   //mqtt 初始化配置
  160.   mqttClient.setServer(mqtt_server, 1883);
  161.   mqttClient.setCallback(aliyun_callback);
  162. }
  163. void loop() {
  164.   if (!mqttClient.connected()) {
  165.     reconnect();
  166.   }
  167.   mqttClient.loop();
  168.   // 主循环中可以添加其他逻辑
  169.   // 固件升级  先不考虑
  170.   // 检测到有人进入 --两种情况,无人,报警
  171.   // 检测有人进入,上报时间,获取时间并上报,同时根据时间判断是否开灯
  172.   // float distance = 0;
  173.   // distance = getDistance();未进行测量似乎结果为不确定的?不为0,不对可能为0
  174.   // Serial.println("当前距离: " + String(distance));
  175.   // if(distance < 50 && distance > 0){ //超声波
  176.   //   //有人经过
  177.   //   //获取时间
  178.   //   // 更新 NTP 时间
  179.   //   timeClient.update();
  180.   //   // 获取当前小时数(24小时制)
  181.   //   int currentHour = timeClient.getHours();
  182.   //   if(currentHour >= 18 || currentHour < 7) { // 如果是18点(即下午6点)以后或早上6点之前
  183.   //     myServo.write(light_on);
  184.   //     Serial.println("It's evening or night, turning the light on.");
  185.   //     delay(500);
  186.   //     myServo.write(90);
  187.   //   } else {
  188.   //     // digitalWrite(ledPin, LOW); // 关灯
  189.   //     myServo.write(light_off);
  190.   //     Serial.println("It's daytime, turning the light off.");
  191.   //     delay(500);
  192.   //     myServo.write(90);
  193.   //   }
  194.   //   //上报消息
  195.   //   unsigned long epochTime = timeClient.getEpochTime();
  196.   //   // 将 Unix 时间戳转换为格式化字符串时间
  197.   //   time_t rawTime = (time_t)epochTime;
  198.   //   struct tm *ptm = gmtime(&rawTime);
  199.   //   char timeStr[30];
  200.   //   strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", ptm);  
  201.   //   // 打印完整的时间字符串
  202.   //   Serial.print("Current date and time: ");
  203.   //   Serial.println(timeStr);
  204.   //   // 发布人员检测消息
  205.   //   String message = "Person detected at " + String(timeStr);
  206.   //   mqttClient.publish(person_detected_topic, message.c_str());
  207.   // }
  208.   delay(200); // 延时20m秒
  209. }
复制代码
代码中都有具体的注释,不懂可以批评区留言。
  1. // 阿里云物联网平台配置
  2. const char* mqtt_server = ""; // 替换为你的阿里云IoT endpoint
  3. const char* mqtt_username = ""; // 替换为你的设备名称和产品Key
  4. const char* mqtt_password = ""; // 替换为你的设备密钥
  5. const char* mqtt_client_id = ""; // 替换为你的客户端ID
  6. // esp32_601
  7. // MQTT主题
  8. const char* servo_topic = ""; // 替换为你的舵机控制主题
  9. const char* person_detected_topic = ""; // 替换为你的人员检测主题
复制代码
这些就是上一节提到的创建设备时,可以查阅的相干连接参数,以及创建产品的时候自定义的消息topic
注意、踩坑: 这里有一个需要注意的地方就是主题这里,从云平台复制出来的主题,往往包含${deviceName}这个部分,这里需要替换为你对应的设备名称,记住是设备名称别复制错了。
  1. /*********/${deviceName}/user/person_detect
  2. 假如你的设备名称是esp32
  3. 替换为
  4. /*********/esp32/user/person_detect
复制代码
另外回调函数中的处置惩罚
  1.   // 处理舵机控制消息  自定义模型,自己定义主题topic和解析,比较简单方便,不需要多余的处理
  2.   if (strcmp(topic, servo_topic) == 0) {
复制代码
这里就是判断,到来的消息是哪个主题的,是否为舵机控制主题的,是才进入舵机主题消息的解析,由于发布的消息大概有别的主题的消息,不判断直接解析大概会出错,不同的topic的解析也不一样,由于数据内容大概不一样。
Android部分和云平台数据流转

剩下的部分就下一篇再先容吧,码字不易,点个赞吧,有什么标题接待批评区留言。
关注我分享更多的实用小计划。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

种地

金牌会员
这个人很懒什么都没写!

标签云

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