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

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

准备工作

先说一下,本人没有单片机经验,以下过程纯靠网上教程+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)

    1
    2
    3
    4
    5
    cd /home
    mkdir emqx-single
    cd emqx-single
    mkdir emqx_data emqx_log
    vim docker-compose.yml

    文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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. 启动项目

    1
    docker compose up -d

    查看容器是否创建成功

    1
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    #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