☰
ESP8266解析电表D0接口SML协议实现MQTT能源监控
2026/10/17 9:48:51 网站建设 项目流程

简介:SMLReader是一款面向物联网开发者与智能家居爱好者的开源电表数据采集网关,基于ESP8266(兼容ESP32)实现D0接口SML协议智能电表数据的实时解析与MQTT远程上报,解决传统电表难以接入Home Assistant、Node-RED等现代家居平台的集成难题。资源包共29个文件,含7张功能示意图(png)、5个核心头文件(.h)封装传感器、MQTT发布、调试与单位转换逻辑,3份README与2份Markdown文档提供配置说明与使用指南,另有YAML/INI环境配置、PlatformIO工程文件及Fritzing电路图(.fzz),整体839KB轻量易部署。已有267人学习下载,读者可直接获取完整Arduino C++工程(含main.cpp、config.h、MqttPublisher.h等模块化源码)、OBIS码映射表、SML帧解析关键逻辑及Wi-Fi+MQTT双模连接范例,快速复现电表数据采集、结构化解析与云端同步全流程。

1. 电表数据不再“黑匣子”:SMLReader 把家用电表的实时功率、总电量、电压电流全吐到 MQTT,连 ESP8266 都能扛住 D0 协议解析

你有没有试过——半夜三点盯着智能插座的功耗曲线,却完全不知道空调压缩机到底在哪个瞬间把电费刷高?不是设备不支持,是家里那台老式机械+电子混合电表(带 D0 接口的 SML 协议电表)压根没联网能力。SMLReader 就是为这种“被遗忘的能源节点”而生:它用一块不到 10 块钱的 ESP8266 模块,接上电表背面那个不起眼的 D0 接口(两根线:GND + DATA),就能把每秒涌出的 SML 报文逐字节拆解,提取 OBIS 标识符对应的真实物理量(比如1-0:1.8.0*255是总正向有功电能,1-0:21.7.0*255是A相实时功率),再通过 MQTT 发到 Home Assistant、Node-RED 或任意自建平台。它不是“读数工具”,而是把电表从一个被动显示设备,变成可编程、可告警、可联动的 IoT 节点。适合想做家庭能源精细化管理、又不想换整块电表的 DIYer,也适合嵌入式初学者练手——协议解析逻辑清晰,Arduino C++ 实现无黑盒,连串口调试时的原始 SML 字节流都给你留好了打印开关。


2. 架构与技术选型:为什么是 ESP8266 + Arduino C++ + D0 物理层,而不是 ESP32 或 Modbus?

SMLReader 的轻量化不是妥协,而是对 D0 协议特性和家庭部署场景的精准匹配。我们先看它拒绝什么:

  • 不用 ESP32:虽然 ESP32 性能更强,但 D0 是异步串行协议(非标准 UART 波特率,典型 9600/300bps),且电表发送 SML 报文是“脉冲式爆发”(一帧报文含数十个 OBIS 条目,持续 200~500ms,之后静默数秒)。ESP8266 的 UART RX 中断+环形缓冲区足矣,加 ESP32 反而增加功耗和固件体积,对常年插在电表旁的网关无意义;
  • 不用 Modbus:国内主流单相/三相智能电表虽支持 RS485 Modbus,但需额外电平转换、布线,且厂家常锁死寄存器地址或要求认证密钥;D0 接口是公开物理层(IEC 62056-21),只要电表有 D0 插座(常见于威胜、科陆、林洋等型号),接线即用;
  • 坚持 Arduino C++:不引入 PlatformIO 复杂配置,不依赖 RTOS,所有核心逻辑(SML 帧同步、OBIS 解析、MQTT 封包)都在SMLParser.cpp和MQTTPublisher.cpp里,函数粒度细到parseOBISValue()和extractHexBytes(),新手改一个 OBIS 地址就能看到效果。

2.1 硬件连接:D0 接口的“两线制”真相与电平适配

D0 接口本质是电流环(Current Loop),非 TTL 电平。电表侧输出的是约 20mA 电流脉冲,通过外部上拉电阻转成电压信号给 ESP8266 识别。这是第一个必须亲手焊的环节:

  • 电表 D0 接口引脚定义(以威胜 DDS353 为例):Pin1 = GND,Pin2 = DATA(开漏输出);
  • ESP8266 连接方式:DATA → GPIO13(RX2,推荐!因 GPIO3/RX0 常被 USB 转串口占用),GND → ESP8266 GND;
  • 关键元件:在 DATA 与 ESP8266 VCC(3.3V)之间接一个4.7kΩ 上拉电阻(非 10k!太大会导致上升沿迟缓,丢帧);
  • 安全提示:绝对禁止将电表 D0 直接连 ESP8266 TX/RX!电表侧是电流源,直接接会烧毁 ESP8266 UART 引脚。上拉电阻是必须的隔离器件。

提示:实物接线后,用万用表测 GPIO13 对地电压,静默时应为 3.3V,电表发码时电压在 0~3.3V 间跳变。若始终为 0V,检查上拉电阻是否虚焊;若始终为 3.3V,检查电表 D0 是否启用(部分电表需进入设置菜单开启 D0 输出)。

2.2 软件栈分层:从物理层到应用层的四层穿透

SMLReader 的代码结构严格按数据流向分层,每一层只解决一个问题:

层级模块文件核心职责关键设计点
物理层HardwareSerial.h+ 自定义D0Receiver.cpp处理 UART 接收、环形缓冲区管理、帧起始检测(/字符)使用HardwareSerial::available()非阻塞轮询,避免delay()导致丢帧;缓冲区大小设为 512 字节(SML 最大帧长 ≈ 380 字节)
协议层SMLParser.cppSML 帧校验(CRC16-XMODEM)、分段(0x01/0x02/0x1E标记)、OBIS 条目提取CRC 计算不调用库函数,手写查表法(crc16_table[]),确保在 ESP8266 上 <50μs 完成
数据层OBISDatabase.cppOBIS 地址映射(如"1-0:1.8.0*255"→"total_energy_kwh")、单位转换(Wh→kWh)、数值类型推导(整数/浮点/字符串)支持运行时动态注册新 OBIS,无需改源码,只需调用registerOBIS("1-0:32.7.0*255", "voltage_l1_v", OBIS_TYPE_FLOAT)
传输层MQTTPublisher.cppMQTT 连接管理(自动重连)、主题生成(home/meter/energy_total)、QoS1 发布、JSON 封包主题前缀可配置(#define MQTT_TOPIC_PREFIX "home/meter"),JSON 不用第三方库,手写sprintf()拼接,减少内存碎片

这种分层让修改变得极简单:想加一个新电表参数?只动OBISDatabase.cpp;想换 MQTT 服务器?只改MQTTPublisher.cpp里的mqtt_server和mqtt_port;想调试原始字节?打开D0Receiver.cpp里的#define DEBUG_SML_RAW 1,串口直接输出十六进制流。

2.3 核心流程图:一个 SML 帧从电表到 MQTT 的 7 步旅程

整个数据链路没有魔法,只有确定性的状态机。以下是真实代码中执行的步骤(已简化异常分支):

  1. 物理接收:D0Receiver::loop()检测到 GPIO13 电平下降沿 → 触发Serial2.read()读取字节 → 存入环形缓冲区rx_buffer[512];
  2. 帧同步:缓冲区扫描到字符/(ASCII 0x2F)→ 记录为帧起始位置;
  3. 帧捕获:继续接收,直到遇到结束标记0x01(STX)+0x02(SOH)+0x1E(SUB)组合 → 截取完整帧;
  4. CRC 校验:取帧末尾 2 字节作为 CRC → 用crc16_xmodem(frame_data, frame_len-2)计算 → 不匹配则丢弃整帧;
  5. OBIS 提取:遍历帧内所有0x72(SML 类型标识)段 → 解析后续 6 字节 OBIS 地址 → 查obis_map[]表获取字段名;
  6. 值解析:根据 OBIS 后续0x09(SCALAR)或0x06(OCTET_STRING)类型 → 调用parseInteger()或parseFloat()→ 单位按数据库预设缩放(如1.8.0除以 1000 得 kWh);
  7. MQTT 发布:拼接 JSON{"value":12345.67,"unit":"kWh","timestamp":1717023456}→client.publish(topic, json_payload, true)(retain=true)。

这个流程在 ESP8266 上平均耗时 12~18ms(实测),远低于电表最小发码间隔(通常 ≥2s),因此不会成为瓶颈。


3. 编译与烧录:Arduino IDE 下零配置跑通,但必须改这 3 个宏定义

SMLReader 的platformio.ini和Arduino IDE项目均可用,但 Arduino IDE 更适合新手——因为它的错误提示更直白。以下步骤基于Arduino IDE 2.3.2 + ESP8266 Core 3.1.0(必须用此版本,低版本无Serial2支持,高版本有 UART 中断兼容问题)。

3.1 环境准备:SDK 版本与板级配置的硬性要求

  1. 打开 Arduino IDE →文件 > 首选项→ 在“附加开发板管理器网址”填入:
    https://arduino.esp8266.com/stable/package_esp8266com_index.json
  2. 工具 > 开发板 > 开发板管理器→ 搜索esp8266→ 安装esp8266 by ESP8266 Community,版本选3.1.0(注意:不是最新版!3.2.0+ 会导致Serial2在某些板子上无法触发中断);
  3. 安装后,工具 > 开发板 > ESP8266 Boards→ 选择Generic ESP8266 Module;
  4. 关键参数设置(必须与硬件匹配):
    • Flash Size:1MB (FS:512KB OTA:~1019KB)
    • Upload Speed:921600(高速上传,避免超时)
    • CPU Frequency:80 MHz(默认,足够)
    • Debug Port:Disabled(节省资源)

注意:若使用 NodeMCU-12E 等带 USB 转串口芯片的开发板,Upload Speed可设为115200;但直接用 CH340 模块烧录时,务必用921600,否则上传失败率极高。

3.2 配置文件修改:3 个#define决定能否连上电表和 MQTT

打开项目根目录下的config.h,必须修改以下三项(其他可保持默认):

// config.h 关键配置段 #define WIFI_SSID "YourHomeWiFi" // 你的 WiFi 名称(2.4GHz only!ESP8266 不支持 5G) #define WIFI_PASSWORD "YourWiFiPass" // WiFi 密码(明文,因 ESP8266 无安全存储) #define MQTT_SERVER "192.168.1.100" // MQTT Broker IP(如 Mosquitto 在树莓派上) #define MQTT_PORT 1883 // 标准端口,若用 TLS 则需改用 8883 并加证书(本项目暂不支持) #define D0_RX_PIN 13 // 必须与硬件接线一致!GPIO13 = RX2

为什么必须改这三项?

  • WIFI_SSID/PASSWORD:ESP8266 启动后首先进入 WiFi 连接状态,若填错,LED 会快闪 3 次后熄灭,无任何串口提示(这是血泪经验:曾因 SSID 多一个空格,调试 2 小时);
  • MQTT_SERVER:不能填域名(如mqtt.local),ESP8266 DNS 解析不稳定,必须填 IP;
  • D0_RX_PIN:默认是13,但若你接在 GPIO4,请同步改为4,否则Serial2根本收不到数据。

3.3 烧录与首次验证:串口监视器里看到/才算成功

  1. 接好硬件(D0 线 + 上拉电阻 + ESP8266 供电);
  2. Arduino IDE 中选择正确端口(工具 > 端口,Windows 显示为COMx,Mac 为/dev/cu.usbserial-xxxx);
  3. 点击右上角√ 验证→ 确认无编译错误;
  4. 点击→ 上传→ 等待进度条完成;
  5. 打开工具 > 串口监视器,设置波特率为115200,换行符选Both NL & CR;
  6. 关键观察点:
    • 启动后应打印Connecting to WiFi...→WiFi connected! IP:192.168.1.xxx;
    • 然后打印Connecting to MQTT...→MQTT connected!;
    • 最重要:几秒后,若电表正在发码,应看到类似:
      [SML] Raw frame start: / [SML] Frame len: 342 bytes, CRC OK [OBIS] 1-0:1.8.0*255 = 12345678 Wh → publish to home/meter/energy_total [OBIS] 1-0:21.7.0*255 = 1245 W → publish to home/meter/power_l1_w
    • 若只看到 WiFi 连接成功,但无[SML]日志 → 检查 D0 接线和上拉电阻;
    • 若看到[SML] Raw frame start: /但无后续Frame len→ 说明收到/但没收到完整帧,大概率是 CRC 校验失败(电表型号不匹配或波特率不对,但 D0 是固定 300/9600,无需设波特率)。

4. 常见问题排查:5 个真实翻车现场与救回操作

SMLReader 的坑不在代码,而在物理世界。以下是我在某高校实验室帮 12 个学生调试时,高频出现的 5 类问题,按现象→原因→解决三步给出答案。

4.1 现象:串口监视器一直打印WiFi connecting...,IP 地址始终为空

原因:ESP8266 无法关联 WiFi,常见于:① WiFi 是 5GHz 频段(ESP8266 仅支持 2.4GHz);② WiFi 密码含特殊字符(如@、$、空格),Arduino 字符串解析失败;③ 路由器开启了“隐藏 SSID”或“WPA3 加密”(ESP8266 Core 3.1.0 仅支持 WPA2)。
解决:

  • 用手机热点(2.4GHz,WPA2-PSK,密码纯字母数字)测试;
  • 在config.h中将密码用双引号包裹,并转义反斜杠:#define WIFI_PASSWORD "MyP@ss123"→ 改为"MyP\\@ss123";
  • 路由器后台关闭“隐藏 SSID”,加密方式选WPA2-PSK [AES]。

4.2 现象:WiFi 和 MQTT 都连上了,但串口无[SML]日志,电表 D0 接口实测有电压跳变

原因:D0 信号电平不匹配。电表 D0 输出是电流环,但部分电表(如早期林洋)输出电压幅值偏低(<2.5V),而 ESP8266 GPIO 输入高电平阈值为0.75×VCC ≈ 2.475V,处于临界区,导致误判。
解决:

  • 将上拉电阻从4.7kΩ换成2.2kΩ(增强上拉能力,抬高高电平);
  • 或在 DATA 线上加一级 74HC14 施密特触发器整形(成本 0.5 元,彻底解决抖动);
  • 快速验证:用示波器看 GPIO13 波形,若上升沿缓慢(>1μs)或高电平 <2.8V,则必换电阻。

4.3 现象:串口偶尔打印[SML] Frame len: XX bytes, CRC OK,但大部分帧显示CRC FAIL

原因:SML 帧在传输中被干扰,最常见于电源噪声。ESP8266 对电源纹波敏感,当电表与 ESP8266 共用同一开关电源时,电表继电器动作产生的瞬态电流会耦合到 ESP8266 供电轨,导致 UART 接收错位。
解决:

  • 必须分离供电:电表 D0 接口取电(通常为 5V)仅用于上拉电阻,ESP8266 用独立 3.3V LDO(如 AMS1117-3.3)供电,输入端加100μF电解电容 +100nF陶瓷电容;
  • 在D0Receiver.cpp中降低接收灵敏度:将if (Serial2.available())改为if (Serial2.available() > 2),跳过单字节噪声。

4.4 现象:MQTT 收到数据,但 Home Assistant 显示unknown,或数值为0

原因:OBIS 地址映射错误。不同厂家电表对同一物理量使用的 OBIS 地址不同。例如:

  • 威胜电表:总电量用1-0:1.8.0*255;
  • 科陆电表:可能用1-0:1.8.1*255(费率1)或1-0:1.8.0*255(总);
  • 而 SMLReader 默认只注册了威胜常用地址。
    解决:
  • 打开OBISDatabase.cpp,找到void initOBISDatabase()函数;
  • 在末尾添加你电表的实际地址:
    registerOBIS("1-0:1.8.1*255", "energy_total_kwh", OBIS_TYPE_UINT32); // 科陆总电量 registerOBIS("1-0:31.7.0*255", "current_l1_a", OBIS_TYPE_FLOAT); // A相电流
  • 重新编译上传,串口会打印新注册的日志。

4.5 现象:设备运行 2 小时后自动重启,串口输出Fatal exception (28)

原因:堆内存溢出。SML 帧解析中String类型滥用(如String sml_frame = Serial2.readString())导致内存碎片,ESP8266 仅 80KB RAM,连续运行后malloc()失败。
解决:

  • 全局搜索项目中所有String声明,替换为char buffer[512]+Serial2.readBytes(buffer, len);
  • 在SMLParser.cpp中,将parseOBISValue()的返回类型从String改为int32_t或float,数值计算在栈上完成;
  • 编译后查看 Arduino IDE 底部内存使用率,确保Global variables≤ 35KB(安全线)。

5. 核心模块代码详解:SML 帧解析与 OBIS 提取的 3 个关键函数

现在我们沉到代码最深处。SMLReader 的灵魂不在 MQTT,而在SMLParser.cpp里那不到 200 行的解析逻辑。下面逐段拆解三个决定成败的函数,每段都附真实注释和参数说明。

5.1bool SMLParser::findSMLFrame(uint8_t *buffer, uint16_t len, uint16_t *frame_start, uint16_t *frame_end)

这是帧同步的守门员,负责从环形缓冲区中捞出完整的 SML 帧。

bool SMLParser::findSMLFrame(uint8_t *buffer, uint16_t len, uint16_t *frame_start, uint16_t *frame_end) { // Step 1: 找帧头 '/' —— SML 标准规定每帧以 ASCII 0x2F 开始 for (uint16_t i = 0; i < len; i++) { if (buffer[i] == 0x2F) { // '/' 字符,ASCII 码 47 *frame_start = i; // Step 2: 从 '/' 向后找帧尾:必须是 0x01 0x02 0x1E 组合(STX+SOH+SUB) // 注意:SML 帧尾固定为这三个字节,且中间不能有其他数据 for (uint16_t j = i + 1; j < len - 2; j++) { if (buffer[j] == 0x01 && buffer[j+1] == 0x02 && buffer[j+2] == 0x1E) { *frame_end = j + 3; // 包含尾部 3 字节 return true; // 找到有效帧 } } } } return false; // 未找到完整帧 }

逻辑说明:

  • 该函数不解析内容,只定位边界。buffer是从D0Receiver拿到的原始字节流,len是当前缓冲区有效长度;
  • *frame_start和*frame_end是输出参数,调用后得到帧在缓冲区中的索引范围;
  • 为什么不用正则?ESP8266 没有 regex 库,且此逻辑 O(n) 时间复杂度,实测 512 字节缓冲区平均 3μs 完成。

5.2uint16_t SMLParser::calculateCRC16(const uint8_t *data, uint16_t len)

CRC 校验是 SML 协议的生命线,错一个字节,整帧报废。

// 预定义 CRC16-XMODEM 查表数组(256 项,占 512 字节 Flash) const uint16_t crc16_table[256] PROGMEM = { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, // ...(完整表略,实际代码中已展开) }; uint16_t SMLParser::calculateCRC16(const uint8_t *data, uint16_t len) { uint16_t crc = 0x0000; for (uint16_t i = 0; i < len; i++) { uint8_t idx = (crc >> 8) ^ data[i]; // 高 8 位异或当前字节 crc = (crc << 8) ^ pgm_read_word_near(&crc16_table[idx]); // 查表更新 } return crc; }

参数说明:

  • data:指向帧数据起始地址的指针(不包含末尾 2 字节 CRC);
  • len:数据长度(SML 帧中,CRC 前的数据长度 = 总长 - 2);
  • pgm_read_word_near():从 Flash 读取查表数据(ESP8266 RAM 紧张,查表必须放 Flash);
  • 关键细节:SML 的 CRC 是校验“从/到0x1E之前的所有字节”,不包括帧头/和帧尾0x01 0x02 0x1E,但包括中间所有0x01/0x02分隔符。

5.3bool SMLParser::parseOBISValue(const uint8_t *obis_ptr, char *value_str, uint8_t max_len)

这是 OBIS 值提取的核心,把二进制编码转成可读数值。

bool SMLParser::parseOBISValue(const uint8_t *obis_ptr, char *value_str, uint8_t max_len) { // OBIS 值结构:[Length][Type][Value],其中 Type=0x09 表示 SCALAR(带缩放因子的整数) uint8_t len = obis_ptr[0]; // 第 1 字节:值长度(不含类型字节) uint8_t type = obis_ptr[1]; // 第 2 字节:类型标识 if (type != 0x09) { // 当前只处理 SCALAR 类型(最常见) return false; } // Step 1: 提取缩放因子(第 3 字节),SML 中为 2 的幂次,如 0xFF = -1 → 10^-1 = 0.1 int8_t scaler = (int8_t)obis_ptr[2]; int32_t raw_value = 0; // Step 2: 解析后续字节为整数(大端序),最多 4 字节 for (uint8_t i = 0; i < len - 2 && i < 4; i++) { raw_value = (raw_value << 8) | obis_ptr[3 + i]; } // Step 3: 应用缩放因子,转成浮点并格式化为字符串 float final_value = (float)raw_value; for (int8_t i = 0; i < scaler; i++) { final_value /= 10.0; } for (int8_t i = 0; i > scaler; i--) { final_value *= 10.0; } // Step 4: 格式化输出,保留小数点后 3 位(避免科学计数法) dtostrf(final_value, 10, 3, value_str); return true; }

逻辑说明:

  • obis_ptr指向 OBIS 条目中0x09类型字节的地址(即0x09及其后的所有字节);
  • scaler是缩放因子,SML 协议中0xFF = -1,0xFE = -2,0x00 = 0,0x01 = 1,表示10^scaler;
  • dtostrf()是 Arduino 标准库函数,将浮点转字符串,10是最小宽度,3是小数位数;
  • 为什么不用String?此处value_str是调用方传入的栈数组(如char val[16]),避免堆分配。

6. 二次开发实战:如何添加「电压跌落告警」并推送 Telegram 消息

现在你已跑通基础功能,下一步是让它真正为你服务。我以「当 L1 相电压低于 210V 持续 5 秒,自动发 Telegram 告警」为例,展示从需求到落地的完整改造链。这不是理论,是我在某导师家真实部署的方案。

6.1 数据源确认:先抓到电压 OBIS 地址

第一步永远不是写代码,而是确认电表真的在发这个数据。打开串口监视器,开启DEBUG_SML_RAW宏(在D0Receiver.cpp中取消注释#define DEBUG_SML_RAW 1),重启设备。你会看到类似:

[SML] RAW: 2F 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34...

复制一段 RAW 数据,粘贴到在线 SML 解析器(如sml-parser.net),找到电压相关 OBIS。常见地址:

  • 1-0:32.7.0*255:L1 相电压(V)
  • 1-0:52.7.0*255:L2 相电压(V)
  • 1-0:72.7.0*255:L3 相电压(V)

确认地址后,在OBISDatabase.cpp中注册:

registerOBIS("1-0:32.7.0*255", "voltage_l1_v", OBIS_TYPE_FLOAT);

6.2 告警逻辑实现:状态机比定时器更可靠

电压跌落是瞬态事件,用millis()定时器容易漏判。我采用有限状态机(FSM):

// 新增全局变量(在 SMLReader.ino 顶部) enum VoltageAlertState { IDLE, DETECTING, ALERTED }; VoltageAlertState alert_state = IDLE; unsigned long voltage_low_start = 0; const unsigned long ALERT_DURATION_MS = 5000; // 持续 5 秒才告警 // 在 loop() 中添加(紧挨着 MQTT 发布之后) float current_voltage = getOBISValue("voltage_l1_v"); // 假设已有此函数 if (current_voltage < 210.0) { if (alert_state == IDLE) { voltage_low_start = millis(); alert_state = DETECTING; } else if (alert_state == DETECTING && (millis() - voltage_low_start) >= ALERT_DURATION_MS) { sendTelegramAlert(current_voltage); alert_state = ALERTED; } } else { // 电压恢复正常,重置状态 alert_state = IDLE; }

为什么用 FSM?

  • DETECTING状态确保只有持续低压才触发;
  • ALERTED状态防止重复发送(避免 5 秒内发 10 条);
  • 全部逻辑在loop()中,无阻塞,不影响 SML 接收。

6.3 Telegram 推送:用 HTTP POST 替代 MQTT(轻量且免服务器)

SMLReader 本身不带 Telegram SDK,但我们用 ESP8266 内置的HTTPClient库即可:

#include <ESP8266HTTPClient.h> void sendTelegramAlert(float voltage) { if (!WiFi.isConnected()) return; HTTPClient http; String url = "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage"; url += "?chat_id=YOUR_CHAT_ID&text="; url += "⚠️ 电压告警:L1相电压 "; url += String(voltage, 1); url += "V,低于210V阈值!"; http.begin(url); int httpCode = http.GET(); // 发送 GET 请求 if (httpCode > 0) { Serial.printf("[Telegram] Alert sent, code: %d\n", httpCode); } else { Serial.printf("[Telegram] Alert failed, code: %d\n", httpCode); } http.end(); }

关键配置:

  • YOUR_BOT_TOKEN:在 Telegram 创建 Bot 后获得(@BotFather);
  • YOUR_CHAT_ID:发消息给 Bot,它会回你自己的 ID(或用getUpdatesAPI 获取);
  • 安全提示:Token 明文写在代码里有风险,生产环境应存入 SPIFFS 文件系统,但 DIY 场景够用。

6.4 验证与上线:三步闭环测试法

改完代码,别急着烧录,按顺序验证:

  1. 单元验证:在sendTelegramAlert()开头加Serial.println("Alert triggered!");,串口看到日志即证明状态机逻辑正确;
  2. 集成验证:用可调电源模拟电表电压输出(接 D0 DATA 线),调至 200V,观察是否 5 秒后发 Telegram;
  3. 长期验证:部署 72 小时,用Serial.print(millis()/3600000)打印运行小时数,确认无内存泄漏重启。

从那以后我每次加新告警,都强制走一遍这三步:先串口打点,再硬件模拟,最后挂机 72 小时。少一次,就可能在家用高峰时错过真正的故障

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询