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

17 阅读9分钟

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

准备工作

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

  1. 拥有自己的服务器

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

  2. 购买ESP8266和SG90舵机

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

    5a1a4b09904b6c0816b8dff4090b4679

  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
    

    image-20251104215107985

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

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

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

    image-20251104215252353

  5. 创建用户

    image-20251104215656590

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

    image-20251104215741653

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

开始操作单片机

修改波特率

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

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

    image-20251026073027955

  2. 修改波特率

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

    image-20251026073150488

编写程序

  1. 打开platformIO➡️PIO Home➡️Platforms,安装Espressif 8266

    image-20251026073539691

  2. 新建项目

    image-20251026073801955

  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

    image-20251026075202416

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

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

    image-20251026075302215

    eea0174cac217d376e3f3aace850ef9b_720

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

    image-20251026075617732

测试

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

    image-20251026075741550

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

    image-20251026075828824

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

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

    image-20251026075951249

编写web页面

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

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

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

image-20251105091807674

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

image-20251104220951348

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

image-20251104221110511

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

部署页面

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

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

  1. 登录cloudflare,选择workers和pages

    image-20251104222830219

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

    image-20251104222902647

    image-20251104223000967

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

    image-20251104223047794

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

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

    image-20251104223123158

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

    image-20251104223313713

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

    image-20251104223618803

  7. 访问

    image-20251104223642492