使用8266+舵机实现物理远程开机

先说原理,就是用ESP8266连接SG90舵机,8266连上网络后接收服务器的命令控制舵机。
我一开始使用的是这个up主-Sha达不溜的方法【开源】笔记本远程开机(纯物理解决方案)哔哩哔哩bilibili,利用点灯科技app实现远程开机,不过不知道是不是点灯科技服务器的问题,有的时候设备不在线,而且延迟比较大,所以问了问AI,转而使用自己的服务器搭建mqtt服务,让8266连接自己的mqtt实现远程开机功能。

有需要云服务器的小伙伴推荐使用雨云 - 新一代云服务提供商,功能很多、很实惠,适合长期使用。除了服务器还提供云应用,云应用可以直接部署EMQX

准备工作

先说一下,本人没有单片机经验,以下过程纯靠网上教程+AI​:rofl:

  1. 拥有自己的服务器

    雨云或者其他服务商处买最便宜的一个服务器就行,如果不买服务器的话就看我最开始说的那个up主的视频操作,使用点灯科技,就不用往下看我的文章了。

  2. 购买ESP8266和SG90舵机

    舵机没啥说的,肯定买不错,8266买这个ch340芯片typec口的,直接用typec线就能烧录,不需要usb转ttl了

  3. 电脑安装vscode+platformIO

    按照教程安装即可VSCode 下 PlatformIO 的安装教程-CSDN博客

    如果过程中安装platform core或者新建项目卡在project wizard请不要关闭vscode,耐心等待即可

  4. 电脑安装CH340串口驱动

    下载地址:CH340

服务器安装EMQX

系统为Debian12

安装docker、docker compose

  1. 使用ssh客户端连接服务器,我这里使用的是WindTerm

  2. 安装docker,按照这个大佬的教程安装即可Debian / Ubuntu 安装 Docker 以及 Docker Compose 教程 - 烧饼博客

部署EMQX

  1. 使用 Docker Compose 部署 EMQX(有1Panel的话可以直接用1Panel部署EMQX)

     cd /home
     mkdir emqx-single
     cd emqx-single
     mkdir emqx_data emqx_log
     vim docker-compose.yml
    

    文件内容如下:

     version: '3'
     ​
     services:
       emqx:
         image: emqx/emqx:6.0.0  # 使用开源版,更轻量
         container_name: emqx
         environment:
           - "EMQX_NODE_NAME=emqx@single-node"  # 随意
           - "EMQX_DASHBOARD__DEFAULT_USERNAME=用户名"
           - "EMQX_DASHBOARD__DEFAULT_PASSWORD=密码"
           - "EMQX_ALLOW_ANONYMOUS=false"  # 禁用匿名连接,提高安全性
         healthcheck:
           test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
           interval: 10s
           timeout: 30s
           retries: 3
         ports:
           - "1883:1883"    # MQTT TCP端口
           - "8083:8083"    # MQTT WebSocket端口
           - "8084:8084"    # MQTT SSL端口
           - "8883:8883"    # MQTT TCP/SSL端口
           - "18083:18083"  # 管理控制台端口
         volumes:
           - ./emqx_data:/opt/emqx/data  # 数据持久化
           - ./emqx_log:/opt/emqx/log    # 日志持久化
         restart: unless-stopped  # 自动重启
         networks:
           emqx-net:
     ​
     networks:
       emqx-net:
         driver: bridge
    
  2. 启动项目

     docker compose up -d
    

    查看容器是否创建成功

     docker ps
    

  3. 放行1883、8083、8084、8883、18083端口

  4. 访问http://ip:18083,即可进入控制台,使用docker-compose.yml文件中配置的用户名密码登录即可

    我这里是已经连上我之前的8286了,所以显示会话是1,刚安装应该都是0才对

  5. 创建用户

  6. 点击添加用户,不需要超级用户权限

  7. 这个用户就是8266和网页登录所需要的用户名和密码

开始操作单片机

修改波特率

  1. 使用typec线将8266与电脑相连

    连接后点击此电脑:right_arrow:管理:right_arrow:设备管理器:right_arrow:端口,可以看到CH340对应的COM几,比如我这里是COM9

  2. 修改波特率

    右键这个端口设备:right_arrow:属性:right_arrow:端口设置,将每秒位数改为115200

编写程序

  1. 打开platformIO​:right_arrow:PIO Home​:right_arrow:Platforms,安装Espressif 8266

  2. 新建项目

  3. 新建项目时间可能会很长,等着就行,可以挂着然后去干别的,项目创建好会自动弹出vscode

  4. 项目创建好后只需要改两个文件

    platformiio.ini

     [env:esp12e]
     platform = espressif8266
     board = esp12e
     framework = arduino
     ; 通过 lib_deps 添加PubSubClient库
     lib_deps = pubsubclient
     upload_speed = 115200
     upload_port = COM8
     monitor_speed = 115200
    

    main.cpp

     #include <Arduino.h>
     #include <ESP8266WiFi.h>
     #include <PubSubClient.h>
     #include <Servo.h>
     ​
     // ************************** 配置区 **************************
     const char* ssid = "想让8266连接的wifi名称";    
     const char* password = "wifi密码";
     const char* mqtt_server = "服务器IP";
     const int mqtt_port = 1883; //EMQX的MQTT的端口号
     const char* mqtt_user = "test"; //添加的用户名
     const char* mqtt_password = "test123"; //添加的用户名对应的密码
     ​
     // ************************** 舵机参数 **************************
     Servo myServo;
     int servoPin = D5;  // 您使用的是D5引脚
     int currentAngle = 90;
     int centerAngle = 90;
     int returnDelay = 500;  //自动回中延迟
     unsigned long lastMoveTime = 0;
     bool autoReturnEnabled = true;
     ​
     // ************************** 全局变量 **************************
     WiFiClient espClient;
     PubSubClient client(espClient);
     bool servoAttached = false;
     ​
     // ************************** 函数声明 **************************
     void setupWifi();
     void callback(char* topic, byte* payload, unsigned int length);
     void reconnect();
     void setServoAngle(int angle);
     void returnToCenter();
     void checkAutoReturn();
     void publishStatus();
     ​
     // ************************** 初始化设置 **************************
     void setup() {
       Serial.begin(115200);
       delay(1000);
       
       Serial.println("初始化S90舵机控制器...");
       Serial.println("引脚配置: D5 (GPIO14)");
       
       // 初始化舵机
       servoAttached = myServo.attach(servoPin, 500, 2400); // 调整脉宽范围以适应S90
       
       if (servoAttached) {
         Serial.println("舵机初始化成功");
         setServoAngle(centerAngle);
       } else {
         Serial.println("舵机初始化失败!请检查连接");
       }
       
       setupWifi();
       client.setServer(mqtt_server, mqtt_port);
       client.setCallback(callback);
       
       Serial.println("初始化完成,等待MQTT连接...");
     }
     ​
     // ************************** 主循环 **************************
     void loop() {
       if (!client.connected()) {
         reconnect();
       }
       client.loop();
       
       checkAutoReturn();
       delay(50);
     }
     ​
     // ************************** 自定义函数 **************************
     ​
     void setupWifi() {
       delay(10);
       Serial.println();
       Serial.print("正在连接WiFi: ");
       Serial.println(ssid);
     ​
       WiFi.begin(ssid, password);
     ​
       int attempts = 0;
       while (WiFi.status() != WL_CONNECTED && attempts < 20) {
         delay(500);
         Serial.print(".");
         attempts++;
       }
     ​
       if (WiFi.status() == WL_CONNECTED) {
         Serial.println("");
         Serial.println("WiFi连接成功!");
         Serial.print("IP地址: ");
         Serial.println(WiFi.localIP());
       } else {
         Serial.println("");
         Serial.println("WiFi连接失败!");
       }
     }
     ​
     // 设置舵机角度(核心函数)
     void setServoAngle(int angle) {
       if (!servoAttached) {
         Serial.println("舵机未正确连接,无法设置角度");
         return;
       }
       
       angle = constrain(angle, 0, 180);
       
       Serial.print("尝试设置舵机角度: ");
       Serial.println(angle);
       
       // 实际控制舵机
       myServo.write(angle);
       delay(100); // 给舵机时间响应
       
       currentAngle = angle;
       lastMoveTime = millis();
       
       Serial.print("舵机角度设置为: ");
       Serial.println(angle);
       
       // 发布状态
       publishStatus();
     }
     ​
     // 发布状态信息
     void publishStatus() {
       if (client.connected()) {
         char statusMsg[100];
         snprintf(statusMsg, sizeof(statusMsg), "角度: %d, 自动回中: %s", 
                  currentAngle, autoReturnEnabled ? "开启" : "关闭");
         client.publish("home/servo/status", statusMsg);
         Serial.print("发布状态: ");
         Serial.println(statusMsg);
       }
     }
     ​
     // 自动回到中心位置
     void returnToCenter() {
       if (currentAngle != centerAngle) {
         Serial.println("执行回中...");
         setServoAngle(centerAngle);
       }
     }
     ​
     // 检查是否需要自动回中
     void checkAutoReturn() {
       if (autoReturnEnabled && 
           currentAngle != centerAngle && 
           (millis() - lastMoveTime) > returnDelay) {
         Serial.println("自动回中触发");
         returnToCenter();
       }
     }
     ​
     // MQTT消息接收回调函数
     void callback(char* topic, byte* payload, unsigned int length) {
       Serial.print("收到主题消息 [");
       Serial.print(topic);
       Serial.print("]: ");
     ​
       // 将payload转换为字符串
       String message = "";
       for (int i = 0; i < length; i++) {
         message += (char)payload[i];
       }
       Serial.println(message);
     ​
       // 调试信息
       Serial.print("当前舵机状态: 已连接=");
       Serial.print(servoAttached);
       Serial.print(", 当前角度=");
       Serial.println(currentAngle);
     ​
       // 处理数字角度指令
       int angle = message.toInt();
       if (angle >= 0 && angle <= 180) {
         Serial.print("解析为角度指令: ");
         Serial.println(angle);
         setServoAngle(angle);
         return;
       }
     ​
       // 处理特殊指令
       if (message == "GET") {
         Serial.println("收到获取状态指令");
         publishStatus();
       }
       else if (message == "AUTO_ON") {
         autoReturnEnabled = true;
         Serial.println("自动回中已开启");
         publishStatus();
       }
       else if (message == "AUTO_OFF") {
         autoReturnEnabled = false;
         Serial.println("自动回中已关闭");
         publishStatus();
       }
       else if (message == "RETURN_NOW") {
         Serial.println("收到立即回中指令");
         returnToCenter();
       }
       else {
         Serial.print("未知指令: ");
         Serial.println(message);
       }
     }
     ​
     void reconnect() {
       static unsigned long lastAttempt = 0;
       if (millis() - lastAttempt < 5000) {
         return;
       }
       lastAttempt = millis();
       
       Serial.print("尝试连接MQTT服务器...");
       
       String clientId = "ESP8266Servo-" + String(random(0xffff), HEX);
       
       if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
         Serial.println("MQTT连接成功!");
         client.subscribe("home/servo/control");
         Serial.println("已订阅主题: home/servo/control");
         
         // 连接成功后发布一次状态
         publishStatus();
       } else {
         Serial.print("连接失败,错误代码: ");
         Serial.println(client.state());
       }
     }
    
  5. 点击vscode左下角这个按钮,选择8266对应的那个口,比如我这里是COM9

  6. 点击左侧的upload按钮上传到8266

    如果出现了进度提示,说明正在上传,如果没有进度提示,而是等待,那么需要你操作一下8266:按住flash情况下按一下rst,然后都松开就行了。

  7. 出现SUCCESS就是上传成功了

测试

  1. 微软商店下载串口调试助手

  2. 打开后选择COM9,波特率115200,然后点击打开按钮

  3. 按一下RST键重启8266,8266会重新连接wifi、mqtt服务,并将代码中输出的内容回显出来,可以看到wifi和mqtt都已连接成功

    如果没成功就重新上传一下或者让AI看看代码有啥问题,上传之前记得先把调试助手的端口关闭,不然vscode连不上8266

编写web页面

就是个静态页面,所以可部署的方式有很多,我先把代码给出来,然后再说怎么部署

代码可在此下载8266操控电脑页面

需要改动的地方就是hanshu.js中的config部分,这里配置的连接emqx的ws的信息,要改成你自己的

外层的index.html是入口页面,computer-control文件夹内的才是8266的控制台

入口页面样式如下,computer-control对应的就是【电脑开机】功能,进入页面会让你输入密码,密码随便填就行,因为我自己有个密码校验接口,不过在文件里我给注释了,你们需要的话可以自己配置密码校验

8266控制台如下,需要先点击【连接服务器】,连接上emqx的mqtt后连接状态会变为【已连接】,然后就可以操作了(目前立即回中、开关自动回中功能、获取状态这几个功能貌似有问题,不过不影响使用)

部署页面

想用域名访问的话可选择的方式有很多,cloudflare或者腾讯的pages、如果有已备案的域名那就直接部署在自己的服务器、没备案的可以使用frp或者cloudflare的tunnels

最简单的就是直接放到cf的pages中,简单说一下pages怎么弄

  1. 登录cloudflare,选择workers和pages

  2. 创建应用程序,选择pages、拖放文件

  3. 部署站点后点击添加自定义域(这里没添加的话没事,之后在项目中也可以设置)

  4. 设置绑定在cloudflare中的域名的二级域名

    比如你在cloudflare中绑定了域名abcd.com那么这里自定义域就可以填diannao.abcd.com

  5. 点击继续,cloudflare会自动添加DNS记录,你不用管,只需要点击激活域即可,过一会就可以使用https域名访问了

  6. 这个样子就说明配置完成了,可以访问了

  7. 访问

3 个赞

硬核教程 :+1:

1 个赞

感谢认可 :grin: