《ESP32 物联网全栈实战-12》完整项目

1 阅读5分钟

第 12 篇:完整项目——智能 WiFi 插座(硬件选型 + 固件架构 + 小程序)

把前 11 篇的知识串起来,做一个真正能用的产品:手机远程开关 + 电量统计 + 定时任务 + 过载保护


1. 硬件选型(BOM)

元件型号单价作用
主控ESP32-C3-MINI-1¥8WiFi + BLE + 控制
继电器SRD-05VDC (5V 触发)¥3通断 220V/10A
电量计量HLW8032¥3.5电压/电流/功率/电量
AC-DC 模块HLK-PM01 (220V→5V)¥6从市电取 5V
LDOAMS1117-3.3¥0.55V→3.3V 供 ESP32
继电器驱动S8050 NPN 三极管¥0.2GPIO 3.3V→5V 驱动
PCB + 外壳嘉立创 + 公模¥5+¥3-
按键 + LED6mm 轻触开关 + 5mm LED¥0.5本地控制 + 状态指示
合计~¥30

2. 电路设计要点

220V AC ─┬─→ HLK-PM01 → 5V → AMS1117 → 3.3V → ESP32
         │
         ├─→ 继电器常开触点 → 插座 L 线输出
         │       │
         │     S8050 ← GPIO (ESP32)
         │
         └─→ HLW8032 ← 采样电阻 (1mΩ) → 插座 N 线
                    │
                    UART(TX) → GPIO (ESP32)

安全注意事项

  • 强电和弱电区域在 PCB 上物理隔离(开槽 + 间距 >6mm)
  • 继电器驱动用三极管隔离(不要直接用 GPIO 驱动 5V 继电器)
  • HLW8032 的采样端是高压侧,注意爬电距离
  • 量产前必须过 3C 认证

3. 固件架构

任务划分(FreeRTOS):

Task (优先级)     | 周期  | 职责
──────────────────────────────────────
HLW_Task (3)     | 1s    | 读 HLW8032 功率/电流/电压
MQTT_Task (2)    | 5s    | 上报电量数据 + 接收开关指令
Relay_Ctrl (3)   | 事件  | MQTT/按键/定时 → 控制继电器
Timer_Mgr (1)    | 1min  | 检查定时任务(8:00开、18:00关)
OTA_Watch (0)    | 10min | 检查云端是否有新固件
LED_Task (1)     | 500ms | WiFi指示灯(快闪=未连接,慢闪=已连接)

核心代码:继电器控制(多源输入统一处理)

// relay_ctrl.c
typedef enum {
    RELAY_SRC_MQTT,      // 手机远程控制
    RELAY_SRC_KEY,       // 物理按键
    RELAY_SRC_TIMER,     // 定时任务
    RELAY_SRC_OVERLOAD,  // 过载保护(优先级最高)
} relay_source_t;

void relay_set(relay_source_t source, bool on) {
    static relay_source_t last_source = RELAY_SRC_MQTT;

    // 过载保护不可被覆盖
    if (last_source == RELAY_SRC_OVERLOAD && source != RELAY_SRC_OVERLOAD) {
        ESP_LOGW("RELAY", "过载保护中,忽略 %d 的开关指令", source);
        return;
    }

    gpio_set_level(RELAY_GPIO, on ? 1 : 0);
    last_source = source;

    // 上报状态
    mqtt_publish("device/relay/status", "{\"relay\":%d,\"source\":\"%s\"}",
                 on ? 1 : 0, relay_source_str(source));
}

4. HLW8032 电量计量

HLW8032 通过 UART 自动上报数据(波特率 4800),每 50ms 一帧。

// hlw8032.c
// 数据帧格式:24 字节,包含电压/电流/功率/累计电量

typedef struct {
    float voltage;       // V
    float current;       // A
    float power;         // W
    float energy;        // Wh(累计)
    float power_factor;  // PF
    bool  data_ready;
} hlw8032_data_t;

void hlw8032_parse(uint8_t *buf, hlw8032_data_t *data) {
    // 校验和检查
    uint8_t checksum = 0;
    for (int i = 0; i < 23; i++) checksum += buf[i];
    if (checksum != buf[23]) return;

    // 解析(按 HLW8032 数据手册)
    uint32_t v_reg = (buf[2]<<16) | (buf[3]<<8) | buf[4];
    uint32_t i_reg = (buf[5]<<16) | (buf[6]<<8) | buf[7];
    uint32_t p_reg = (buf[8]<<16) | (buf[9]<<8) | buf[10];

    // 电压系数 ≈ 1.88×10⁻⁶
    data->voltage = v_reg * 1.88e-6f;
    data->current = i_reg * 3.47e-5f;
    data->power   = p_reg * 6.53e-5f;
    data->data_ready = true;
}

5. 定时任务管理

// timer_mgr.c —— 简易 Cron 实现
typedef struct {
    uint8_t hour;
    uint8_t minute;
    bool    action;  // true=开, false=关
    bool    enabled;
    uint8_t repeat;  // 0=单次, 1=每天, 2=工作日
} timer_task_t;

#define MAX_TIMERS 8
static timer_task_t timers[MAX_TIMERS];

// MQTT 接收定时设置
// topic: device/timer/set
// payload: {"id":0,"hour":8,"minute":0,"action":true,"repeat":1}

void timer_mgr_check(void) {
    time_t now;
    struct tm timeinfo;
    time(&now);
    localtime_r(&now, &timeinfo);

    for (int i = 0; i < MAX_TIMERS; i++) {
        if (!timers[i].enabled) continue;
        if (timers[i].hour == timeinfo.tm_hour &&
            timers[i].minute == timeinfo.tm_min) {
            relay_set(RELAY_SRC_TIMER, timers[i].action);
            ESP_LOGI("TIMER", "定时任务触发了 → %s",
                     timers[i].action ? "ON" : "OFF");

            if (timers[i].repeat == 0) {
                timers[i].enabled = false;  // 单次任务,执行后禁用
            }
        }
    }
}

时间同步:用 SNTP 从 NTP 服务器获取时间:

#include "esp_sntp.h"

void sntp_init(void) {
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "ntp.aliyun.com");
    esp_sntp_setservername(1, "pool.ntp.org");
    esp_sntp_init();

    // 等时间同步(最多等 10 秒)
    int retry = 0;
    while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < 100) {
        vTaskDelay(pdMS_TO_TICKS(100));
    }
    setenv("TZ", "CST-8", 1);  // 东八区
    tzset();
}

6. 小程序界面

增加定时设置页面和电量统计页面:

底部 TabBar:
┌──────────┬──────────┬──────────┐
│   首页   │  定时    │   统计   │
│ 开关+功率│ 定时列表 │ 今日/月用电│
└──────────┴──────────┴──────────┘

首页:实时功率(W)+ 今日用电(kWh)+ 开关按钮 + 在线状态

定时页:定时任务列表 + 添加按钮(时间选择器 + 重复模式选择 + 开关选择)

统计页:ECharts 柱状图(每日用电量) + 本月累计


7. 过载保护——安全是 IoT 产品的底线

// 在 HLW_Task 中
if (data.power > 2500.0f) {  // 超过 2500W
    relay_set(RELAY_SRC_OVERLOAD, false);  // 强制断开
    mqtt_publish("device/alert", 
        "{\"type\":\"overload\",\"power\":%.1f,\"msg\":\"过载保护:已自动断电\"}",
        data.power);
}

8. 这个项目能延伸做什么?

把插座里的 HLW8032 和继电器换成别的,架构完全复用:

智能插座 → 智能灯(继电器 + 亮度传感器)
         → 自动浇花(电磁阀 + 土壤湿度传感器)
         → 远程设备监控(电机驱动器 + CAN 总线)
         → 环境监测仪(PM2.5 + CO2 + 温湿度)

下一篇:量产 Checklist——从原型到产品,一条不能少