本篇博客将完整记录如何使用 ESP8266 (NodeMCU) 固件,利用 Lua 语言配合巴法云,最终实现小爱同学语音控制舵机转动的全流程。


准备工作

  1. 硬件接线 (以 SG90 舵机为例)
    红线 (VCC) ➡️ ESP8266 的 3V3(若动力不足可接 VIN)
    棕线 (GND) ➡️ ESP8266 的 GND
    橙线 (信号线) ➡️ ESP8266 的 D1 引脚(对应 Lua 底层 GPIO 5)
  2. 巴法云与米家端配置
    由于米家生态不对个人开发者直接开放,我们需要通过巴法云进行 MQTT/TCP中转:
    a. 注册巴法云:登录后台拿到你的唯一密钥 Private_Key(私钥)。
    b. 新建 MQTT设备云:新建一个主题,命名为 9nfwp3dxs002。
    c. 修改昵称:在巴法云后台将该主题的昵称修改为 “舵机” 或 “灯”等随便名字(小爱同学以此名字来识别语音指令控制舵机)。
    d. 绑定米家:打开手机 米家App ➡️ 我的 ➡️ 其他平台设备 ➡️ 绑定“巴法” 账号。随后在列表中找到该设备,分配房间并同步。

一、开发lua环境搭建

  1. 如果要使用nodemcu固件的烧录且使用Lua在esp8266上跑,需要先下载定制固件,由于我需要连接mqtt和使用舵机,所以我额外勾选MQTT和PWM。在这里插入图片描述然后填写自己的邮箱就可以Start your bulid,注意是第二封finish邮件中找到在这里插入图片描述
    上述两个的bin文件任意一个都行,integer稍微大小要小一点。
  2. 使用 NodeMCU PyFlasher 写入固件。
    NodeMCU firmware:点击 Browse,选择你下载好的 .bin 固件文件
    Baud rate波特率:选择 115200
    Flash mode:选择 Dual I/O (DIO)
  3. 直接在VS Code左侧扩展市场搜索并安装 NodeMCU-Tools 插件,然后再 VS Code 中打开一个空文件夹(作为你的项目工作区),并在里面新建一个文件,命名为 init.lua
    为什么要以 init.lua命名? NodeMCU 芯片在上电开机后,底层系统会自动寻找并第一个执行名为 init.lua 的文件,它是整个程序的入口。

二、代码编写

local WIFI_SSID = “WIFI名称”
local WIFI_PASS = “WIFI密码”
local BEMFA_UID = “巴法云个人私钥”
local TOPIC = “巴法云订阅MQTT主题”

1.舵机初始化与控制

这里我就假设转动两个角度一个是90和0来模拟开灯和关灯,代码如下:

-- 50Hz 周期(20ms)
-- 90(1.5ms)  -> 占空比约 76
pwm.setup(SERVO_PIN, 50, 76) 
pwm.start(SERVO_PIN)

function setServoAngle(angle)
    if angle == 90 then
        pwm.setduty(SERVO_PIN, 76) -- 90度
    elseif angle == 0 then
        pwm.setduty(SERVO_PIN, 25) -- 0度
    end
    print("执行动作: 舵机转动到 " .. angle .. " 度")
end

关于占空比的数学换算:
NodeMCU 的 PWM 分辨率为 10 bit(范围 0 - 1023)。
0 ∘ 0^\circ 0 对应 0.5 ms 0.5\text{ms} 0.5ms 脉宽: 0.5 ms 20 ms × 1023 ≈ 25.5 \frac{0.5\text{ms}}{20\text{ms}} \times 1023 \approx 25.5 20ms0.5ms×102325.5(代码取 25)
90 ∘ 90^\circ 90 对应 1.5 ms 1.5\text{ms} 1.5ms 脉宽: 1.5 ms 20 ms × 1023 ≈ 76.7 \frac{1.5\text{ms}}{20\text{ms}} \times 1023 \approx 76.7 20ms1.5ms×102376.7(代码取 76)
这里我进行软件的粗略计算

2.巴法云 MQTT 连接逻辑

巴法云是一个个人开发者的免费物联网云平台。本段函数处理了从“握手连接”到“订阅监听”的全套逻辑。代码如下:

local mqtt_client = nil

function connectBemfa()
    if mqtt_client ~= nil then
        mqtt_client:close() -- 清理旧实例,防止内存泄漏
    end

    print("正在连接巴法云 MQTT 服务器...")
    mqtt_client = mqtt.Client(BEMFA_UID, 120, BEMFA_UID, BEMFA_UID)

    -- 事件1:收到云端指令
    mqtt_client:on("message", function(client, topic, data)
        print("收到控制指令 [" .. topic .. "]: " .. data)
        if data == "on" then
            setServoAngle(90)
        elseif data == "off" then
            setServoAngle(0)
        end
    end)

    -- 事件2:掉线重连
    mqtt_client:on("offline", function(client)
        print("MQTT 已断开连接,5秒后尝试重连...")
        tmr.create():alarm(5000, tmr.ALARM_SINGLE, connectBemfa)
    end)

    -- 发起 TCP 连接
    mqtt_client:connect("bemfa.com", 9501, false, function(client)
        print("已成功建立 MQTT TCP 连接!")
        -- 核心:连接后必须订阅主题
        client:subscribe(TOPIC, 0, function(client) 
            print("成功订阅主题: " .. TOPIC)
        end)
    end, function(client, reason)
        print("MQTT 连接失败,底层错误码: " .. reason)
    end)
end

3. WiFi 连接

wifi.setmode(wifi.STATION)
wifi.sta.config({ssid=WIFI_SSID, pwd=WIFI_PASS, auto=true})

-- 监听1:物理连接成功
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
    print("已物理连接到路由器,等待 DHCP 分配 IP...")
end)

-- 监听2:成功获取 IP(真正的联网成功)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
    print("WiFi 连接彻底成功! 设备 IP 地址: " .. T.IP)
    connectBemfa() -- 联动触发巴法云连接
end)

-- 监听3:断开或失败(智能诊断)
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
    print("WiFi 连接断开或失败! -> 底层错误代码 (Reason): " .. T.reason)
    if T.reason == 201 then
        print("   -> 诊断: 找不到指定的 WiFi。请检查是否误填了 5G 信号,或名字拼写错误!")
    elseif T.reason == 202 then
        print("   -> 诊断: WiFi 密码错误!")
    elseif T.reason == 200 or T.reason == 203 then
        print("   -> 诊断: 路由器拒绝接入或信号太弱。")
    end
end)

完整代码

-- ==========================================
-- 1. 全局参数配置
-- ==========================================
local WIFI_SSID = "WIFI名称"  
local WIFI_PASS = "WIFI密码"
local BEMFA_UID = "巴法云个人私钥" 
local TOPIC    = "巴法云订阅MQTT主题"
local SERVO_PIN  = 1 -- 对应esp8266的D1引脚                  

-- ==========================================
-- 2. 舵机初始化与控制函数
-- ==========================================
-- 50Hz 周期(20ms)
-- 0(0.5ms)   -> 占空比约 25
-- 90(1.5ms)  -> 占空比约 76
pwm.setup(SERVO_PIN, 50, 76) 
pwm.start(SERVO_PIN)

function setServoAngle(angle)
    if angle == 90 then
        pwm.setduty(SERVO_PIN, 76) -- 90度
    elseif angle == 0 then
        pwm.setduty(SERVO_PIN, 25) -- 0度
    end
    print("执行动作: 舵机转动到 " .. angle .. " 度")
end

-- ==========================================
-- 3. 巴法云 MQTT 连接逻辑
-- ==========================================
local mqtt_client = nil

function connectBemfa()
    -- 如果旧实例存在,先清理避免内存泄漏
    if mqtt_client ~= nil then
        mqtt_client:close()
    end

    print("正在连接巴法云 MQTT 服务器...")
    -- 初始化 Client (Client_ID, KeepAlive, Username, Password)
    -- 巴法云规则:这四个参数除了 KeepAlive,其余全部填 UID
    mqtt_client = mqtt.Client(BEMFA_UID, 120, BEMFA_UID, BEMFA_UID)

    -- 注册:收到云端指令事件
    mqtt_client:on("message", function(client, topic, data)
        print("收到控制指令 [" .. topic .. "]: " .. data)
        if data == "on" then
            setServoAngle(90) -- 例如:小爱同学下发打开指令 $\rightarrow$ 舵机转到90度
        elseif data == "off" then
            setServoAngle(0)  -- 例如:小爱同学下发关闭指令 $\rightarrow$ 舵机转到0度
        end
    end)

    -- 注册:掉线重连事件
    mqtt_client:on("offline", function(client)
        print("MQTT 已断开连接,5秒后尝试重连...")
        tmr.create():alarm(5000, tmr.ALARM_SINGLE, connectBemfa)
    end)

    -- 发起连接 (域名: bemfa.com, 端口: 9501, false表示明文非加密)
    mqtt_client:connect("bemfa.com", 9501, false, function(client)
        print("已成功建立 MQTT TCP 连接!")
        
        -- 核心步骤:成功连接后必须订阅主题,巴法云才会显示设备在线
        client:subscribe(TOPIC, 0, function(client) 
            print("成功订阅主题: " .. TOPIC)
            print("设备已完全上线!现在可以去巴法云网页端或联动音箱发送 on/off 测试了。")
        end)
        
    end, function(client, reason)
        print("MQTT 连接失败,底层错误码: " .. reason)
    end)
end

-- ==========================================
-- 4. WiFi 连接
-- ==========================================
print("====================================")
print("开始配置 WiFi...")
wifi.setmode(wifi.STATION)
wifi.sta.config({ssid=WIFI_SSID, pwd=WIFI_PASS, auto=true})

-- 监听事件:WiFi 成功连接到路由器 (尚未获取IP)
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
    print("已物理连接到路由器,等待 DHCP 分配 IP...")
end)

-- 监听事件:成功获取到 IP (此时真正具备网络通信能力)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
    print("WiFi 连接彻底成功! 设备 IP 地址: " .. T.IP)
    connectBemfa() -- 网络通畅后,自动触发巴法云连接
end)

-- 监听事件:WiFi 断开或连接失败 (诊断助手)
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
    print("WiFi 连接断开或失败!")
    print("   -> 底层错误代码 (Reason): " .. T.reason)
    
    if T.reason == 201 then
        print("   -> 诊断: 找不到指定的 WiFi。请检查是否误填了 5G 信号,或名字拼写错误!")
    elseif T.reason == 202 then
        print("   -> 诊断: WiFi 密码错误!")
    elseif T.reason == 200 or T.reason == 203 then
        print("   -> 诊断: 路由器拒绝接入或信号太弱。")
    end
end)

总结

先烧录好bin文件到esp8266后,在restart,通过vscode打开上面的lua代码文件右键选择Upload activate file to device,就可以连接小爱同学进行语音控制舵机转动!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐