第 12 篇:完整项目——智能 WiFi 插座(硬件选型 + 固件架构 + 小程序)
把前 11 篇的知识串起来,做一个真正能用的产品:手机远程开关 + 电量统计 + 定时任务 + 过载保护。
1. 硬件选型(BOM)
| 元件 | 型号 | 单价 | 作用 |
|---|---|---|---|
| 主控 | ESP32-C3-MINI-1 | ¥8 | WiFi + BLE + 控制 |
| 继电器 | SRD-05VDC (5V 触发) | ¥3 | 通断 220V/10A |
| 电量计量 | HLW8032 | ¥3.5 | 电压/电流/功率/电量 |
| AC-DC 模块 | HLK-PM01 (220V→5V) | ¥6 | 从市电取 5V |
| LDO | AMS1117-3.3 | ¥0.5 | 5V→3.3V 供 ESP32 |
| 继电器驱动 | S8050 NPN 三极管 | ¥0.2 | GPIO 3.3V→5V 驱动 |
| PCB + 外壳 | 嘉立创 + 公模 | ¥5+¥3 | - |
| 按键 + LED | 6mm 轻触开关 + 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——从原型到产品,一条不能少