简介:这是一份基于ESP8266的智能热泵控制系统完整源码包,面向嵌入式开发者、物联网爱好者和智能家居项目实践者。系统集成了WiFi通信、MQTT消息收发、Web交互界面、WiFi配置管理以及OTA远程固件升级,适合作为接入Home Assistant等平台的参考实现,也可用于学习ESP8266网络编程与设备远程维护,兼顾功能完整性与代码可读性。资源共15个文件,包含6个C++源文件、3个头文件,以及README、PlatformIO配置、原理图等辅助材料,结构清晰,覆盖核心逻辑、Web服务、MQTT和WiFi配置模块;压缩包约579KB,轻量易部署。已有53人学习浏览,源码可在PlatformIO环境直接编译烧录,并结合项目说明快速理解从设备注册、状态上报到远程控制命令处理的完整链路。对希望掌握ESP8266+MQTT+WebServer综合开发,或需要一套可扩展热泵控制框架的读者,具备不错的参考与复用价值。 直接做这套基于ESP8266的智能热泵控制系统,其实是我个人捣鼓很久的一个项目。当时家里装了一台空气能热泵热水器,原厂控制面板功能太死板,只能设定固定温度和定时,想远程看水温、回家前提前启动加热基本没戏。后来一查,热泵主机控制逻辑也不算复杂,核心就是根据水温控制压缩机启停,于是决定用ESP8266自己做一套控制系统,顺便把源码整理出来分享。
这个项目适合谁?如果你是搞嵌入式的小伙伴,想练手WiFi控制、传感器采集、继电器驱动这一整套物联网闭环,或者家里正好有热泵、电热水器想改成智能控制,这套代码可以直接拿过去抄作业。整个系统成本也就三四十块钱,比买成品智能插座加温度计组合靠谱得多。
1. 项目整体思路与系统架构
1.1 为什么是ESP8266而不是其它方案
选型的时候我其实纠结过几套方案:STM32加蓝牙模块、树莓派加继电器、ESP32、ESP8266。后来综合比较,ESP8266是最适合的。
首先是价格,一块ESP8266开发板裸板几块钱,带USB转串口的NodeMCU板子也就十几块,相比STM32需要额外买WiFi芯片(比如ESP8285或AT指令模块),成本低得多。其次是生态,Arduino框架下ESP8266的开发资料非常多,库函数齐全,网上能搜到的项目案例数以万计。
还有个关键原因:热泵控制对实时性要求没那么苛刻。压缩机启动是个秒级动作,水温变化以分钟甚至小时计,ESP8266的主频80MHz虽然不算高,但处理这种低频控制场景绰绰有余。相比之下,ESP32在这个项目里有点性能过剩,而且功耗也高一些。
1.2 系统功能拆解与整体流程
这套系统实现的功能,对照着热泵主机的实际工作逻辑来拆:
- 水温实时采集:热泵系统里,最关键的是水箱温度,用DS18B20防水探头插在水箱测温管里,精度正负0.5摄氏度,完全够用。
- 温度显示与设定:用0.96寸OLED显示屏,实时显示当前水温、设定温度、工作状态。
- 继电器控制压缩机:通过一个5V继电器模块控制热泵主机的电源通断,达到设定温度就断电停压缩机,低于回差温度重新上电启动。
- 远程监控与控制:手机连上局域网或者通过公网MQTT服务器,随时查看水温状态,远程调整目标温度。
- 本地按键交互:两个物理按键,一个调高温度,一个调低温度,方便家里老人不依赖手机也能操作。
整体数据流是这样的:DS18B20采集水温,ESP8266读取数据后做滤波和格式化,一方面送OLED显示,另一方面与设定温度比较,通过PID或回差逻辑决定继电器开关状态;同时通过WiFi建立到MQTT服务器的长连接,周期上报状态数据,接收手机端下发的控制指令。
1.3 源码目录结构与核心模块
压缩包里我做了比较清晰的目录划分,方便你直接找到需要的部分:
smart_heatpump/ ├── smart_heatpump.ino # 主程序入口 ├── config.h # 全局配置(WiFi、MQTT、引脚定义) ├── temperature.h/.cpp # DS18B20采集模块 ├── relay_control.h/.cpp # 继电器控制模块 ├── display.h/.cpp # OLED显示模块 ├── button.h/.cpp # 按键处理模块 ├── mqtt_handler.h/.cpp # MQTT通信模块 └── README.md # 接线说明和配置指南这里要说明的是,很多人拿到源码喜欢直接往Arduino IDE里灌,结果一堆头文件找不到。分包编译是必须的,.ino文件所在的文件夹名字必须和.ino文件名保持一致,而且所有.h和.cpp文件放在同一个文件夹里,编译时Arduino IDE才会自动识别。
2. 核心源码解析与关键实现
2.1 WiFi连接与断线重连机制
WiFi连接这块,我踩过的坑比想象中多。ESP8266的WiFi库看着简单,但实际使用中你会发现,如果路由器信号不稳定或者密码错误,它会一直卡在连接状态,WiFi.status()迟迟不返回WL_CONNECTED。
我的处理方式是加了一个重连计数器和状态机:
#include <ESP8266WiFi.h> bool connectWiFi() { int retryCount = 0; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED && retryCount < 30) { delay(500); retryCount++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("[WiFi] Connected, IP: " + WiFi.localIP().toString()); return true; } else { Serial.println("[WiFi] Connection failed, retry later."); return false; } }注意一个细节:ESP8266的WiFi.begin()是非阻塞的,它启动连接过程后立即返回,状态变化是异步的。所以必须用轮询方式等待。30次重试,每次500毫秒,总共15秒超时,这个时间窗口基本覆盖了路由器DHCP分配IP所需的时间。
还有一个重要操作是设置WiFi.setSleepMode(WIFI_NONE),关掉ESP8266的省电模式。ESP8266默认的modem sleep会导致WiFi传输延迟骤增,甚至MQTT心跳包超时断开,对实时控制场景来说非常致命。代价是功耗从几毫安涨到70毫安左右,但控温系统是长期接电源的,这点功耗无所谓。
2.2 温度采集与多点滤波算法
DS18B20是单总线协议,数据线同时传输时钟和数据,时序要求比较严格。用OneWire库和DallasTemperature库可以省去底层时序的麻烦,但读取速度有个问题:每次转换温度需要750毫秒(12位精度),如果你有多路传感器,串行读取会非常耗时。
我的方案是在主循环里用非阻塞方式轮询:
#include <OneWire.h> #include <DallasTemperature.h> #define ONE_WIRE_BUS D4 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); float readCurrentTemp() { sensors.requestTemperatures(); float tempC = sensors.getTempCByIndex(0); if (tempC == DEVICE_DISCONNECTED_C) { Serial.println("[Sensor] DS18B20 disconnected!"); return lastValidTemp; } // 简单滑动滤波,去除抖动 filteredTemp = filteredTemp * 0.8 + tempC * 0.2; lastValidTemp = filteredTemp; return filteredTemp; }需要注意的点是DS18B20在长线传输时容易受到干扰,尤其是和继电器控制线绑在一起走线时,继电器吸合瞬间的电磁脉冲会导致温度读数跳变到85℃或者-127℃。我实测定下来,传感器线最好用屏蔽双绞线,屏蔽层单端接地,而且尽量远离220V交流线。
滑动滤波系数0.8和0.2是我实际调出来的,兼顾了响应速度和稳定性。滤波系数如果太小(比如0.95对0.05),温度变化响应会非常滞后,热泵压缩机启停判断就会迟钝,可能导致水温过冲。这个系数在传感器数据变化频繁的场景,需要根据实际情况重新整定。
2.3 温度控制逻辑:回差控制与防频繁启停
热泵压缩机最怕频繁启停。压缩机启动电流大,频繁启停会导致电机过热、寿命缩短,甚至烧毁启动电容。所以控制逻辑里绝对不能简单地“低于设定温度就启动,到了就停止”,必须加入回差控制。
回差控制的原理很简单:设定目标温度28℃,回差温度3℃,那么水温降到25℃时才启动压缩机,升到28℃时停止。这样压缩机每次运行至少能把水温提升3℃,避免临界温度附近的反复震荡。
void updateRelay(boolean &relayStatus, float currentTemp, float targetTemp) { const float hysteresis = 3.0; if (!relayStatus && currentTemp <= (targetTemp - hysteresis)) { relayStatus = true; digitalWrite(RELAY_PIN, HIGH); Serial.println("[Control] Relay ON - Compressor start."); } if (relayStatus && currentTemp >= targetTemp) { relayStatus = false; digitalWrite(RELAY_PIN, LOW); Serial.println("[Control] Relay OFF - Compressor stop."); } }这里有个细节:继电器模块的触发逻辑分高电平触发和低电平触发,代码里必须和实际硬件一致。我用的模块是低电平触发,所以代码里relayStatus为true时digitalWrite(RELAY_PIN, LOW)。如果你买的是高电平触发的模块,一定要把HIGH和LOW反过来,这个粗心导致我debug了一个小时才找到原因。
2.4 MQTT通信协议封装与JSON解析
远程控制是本项目的亮点。考虑到局域网内HTTP请求轮询太浪费资源,我选择了MQTT协议,基于发布/订阅模式,非常契合设备状态上报和指令下发场景。
MQTT的核心概念是主题(Topic)。我定义了两类主题:
smart_heatpump/status:设备向服务器发布状态信息,载荷是JSON格式字符串,频率为每30秒一次。smart_heatpump/set:设备订阅服务器的指令主题,手机端控制温度就向这个主题发布JSON数据。
MQTT客户端用PubSubClient库,这个库在ESP8266上跑得很稳。关键配置是设置keepalive心跳间隔为30秒,这样理论上断线检测最长需要约45秒(2倍心跳间隔),配合重连机制可以实现自动恢复。
#include <PubSubClient.h> void mqttCallback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) { message += (char)payload[i]; } if (String(topic) == "smart_heatpump/set") { // 解析JSON指令 DynamicJsonDocument doc(256); DeserializationError error = deserializeJson(doc, message); if (!error && doc.containsKey("target_temp")) { targetTemp = doc["target_temp"].as<float>(); EEPROM.write(0, targetTemp); EEPROM.commit(); } } }JSON解析我用的是ArduinoJson库,版本6.x。注意几个坑:一是DynamicJsonDocument要分配足够的内存,但也不能太大,ESP8266的堆内存总共也就80KB可用,一个256字节的JSON文档就够了;二是deserializeJson返回的DeserializationError必须检查,否则你解析乱数据时会得到一堆垃圾值。
3. 硬件选型与接线实操
3.1 关键元器件清单
这套系统用到的元器件不多,但每个都有讲究:
| 元件 | 型号/规格 | 数量 | 选型理由 |
|---|---|---|---|
| 主控板 | NodeMCU V3 (ESP8266) | 1 | 内置USB转串口,直接烧录方便 |
| 温度传感器 | DS18B20防水探头 | 1 | 测温范围宽,防水封装适合水箱 |
| 继电器模块 | 5V 1路低电平触发 | 1 | 控制热泵电源通断,隔离控制信号 |
| 显示屏 | 0.96寸 I2C OLED | 1 | 功耗低,显示清晰,I2C省引脚 |
| 降压模块 | AC-DC 220V转5V 2A | 1 | 长期稳定供电,带过载保护 |
| 按键 | 轻触开关 | 2 | 本地温度调节 |
| 电阻 | 10k欧姆 | 1 | DS18B20上拉电阻,信号稳定 |
| 接线端子 | 5.08mm间距 | 若干 | 强电连接可靠,方便维护 |
这里重点说下电源。很多新手做ESP8266项目直接用USB供电,但继电器模块拉合瞬间电流尖峰很大,通过USB口供电容易导致ESP8266复位重启,表现为控制过程中系统突然重启,保护机制完全没有作用。
我的做法是用一个220V转5V 2AAC-DC降压模块单独供电。5V输出分两路:一路直接给继电器模块供电,另一路经过AMS1117-3.3降成3.3V给NodeMCU的VIN引脚供电。这种方案下,继电器大电流波动不会直接干扰到ESP8266的3.3V电源轨,系统稳定性明显提升。
3.2 接线方案与信号隔离
接线其实不复杂,但有几个位置必须细心处理。
先看引脚分配:
NodeMCU引脚 连接设备 D1 (GPIO5) OLED SCL D2 (GPIO4) OLED SDA D3 (GPIO0) 按键1(温度+) D4 (GPIO2) 按键2(温度-) D5 (GPIO14) DS18B20 DATA D6 (GPIO12) 继电器模块IN VIN (5V) OLED VCC、继电器VCC 3V3 传感器VCC GND 所有设备GND继电器模块的控制信号GND必须和ESP8266的GND共地,否则逻辑电平参考基准不一致,继电器无法可靠触发。这个问题在独立供电方案中尤其容易出现,接完线务必用万用表确认两路GND导通。
强电部分,继电器触点串联在热泵主机的火线上,零线直连。这里必须注意:继电器只是控制通断,不具备漏电保护功能,所以热泵的供电回路中一定要保留原有的空气开关或漏电保护器。安全是底线,不能省。
4. 固件编译与烧录全流程
4.1 开发环境配置
推荐用Arduino IDE加ESP8266开发板包,操作门槛最低。打开IDE,在“文件->首选项->附加开发板管理器网址”里填入:
http://arduino.esp8266.com/stable/package_esp8266com_index.json然后在“工具->开发板->开发板管理器”里搜索esp8266,安装最新版本。这一步如果网络慢,可以挂镜像源,否则下载几十MB的包很容易超时失败。
安装完成后,在“工具->开发板”里选择NodeMCU 1.0 (ESP-12E Module)。烧录参数我建议:
- CPU频率:160MHz,比默认80MHz快一倍,JSON解析和MQTT收发更流畅。
- Flash大小:4M (3M SPIFFS),默认即可。
- Upload speed:115200,不需要加快到921600,提高烧录成功率。
4.2 固件烧录与启动日志解读
NodeMCU板子自带USB转串口,驱动通常是CH340G,Windows上自动识别。烧录前先拔掉D3和D4上连接的按键线,因为GPIO0和GPIO2在烧录时是高电平还是低电平的状态会影响进入烧录模式。
具体做法:按住开发板上的FLASH按键不放,插上USB线,过两秒再松开FLASH按键,然后点击Arduino IDE的“上传”按钮。如果时序错乱,Serial Monitor会一直刷“warning: espcomm_sync failed”,重复尝试就好。
烧录完成后,打开串口监视器,波特率设9600,正常情况下应该看到启动日志:
[WiFi] Connected, IP: 192.168.1.123 [Sensor] DS18B20 init success, address: 28FF6D4B2E17... [Control] Relay OFF - Compressor stop. [MQTT] Connected to broker, state: 0看到这四行日志,基本说明系统核心链路已经通了。如果卡在WiFi连接这一步,优先排查密码是否包含特殊字符导致转义问题,ESP8266的WiFi.begin对包含引号或反斜杠的密码处理有坑,建议用不含特殊字符的密码测试。
4.3 参数调优与现场调试
系统跑通后就是调参数。个人经验是水温回差设为3℃到5℃。回差太小,压缩机会在设定点附近频繁启动,电费飙升还伤设备;回差太大,水温波动明显,洗澡体验差。
还要注意MQTT的心跳保活机制。如果家用的WiFi路由器开启了AP隔离或客户端隔离功能,设备之间无法互通,手机App可能连不上MQTT服务器,但设备本身能连外网。排查时先确认手机和设备是否在同一网段,能否互相ping通。
5. 常见问题与排查技巧实录
5.1 典型故障速查表
我梳理了几个高频问题,每个都是实打实踩过的坑:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 上电后OLED无显示 | I2C地址不对或接线错误 | 用I2C扫描代码查地址,常见是0x3C或0x3D |
| DS18B20读数85℃ | 传感器未接好或线序错误 | 检查数据线上拉电阻,确认VCC和GND没接反 |
| 继电器乱跳 | 控制逻辑滤波不够 | 增强软件滤波,确认回差设置合理 |
| 手机远程没反应 | MQTT断开或IP变化 | 串口看MQTT状态码,检查路由器DHCP静态绑定 |
| WiFi频繁掉线 | 电源纹波干扰或信号弱 | 加1000uF电解电容到VIN,调整天线方向 |
| 水温显示不变 | 传感器位置没泡到水里 | 确认探头在水箱测温盲区外 |
5.2 防灰防潮与长期稳定性
热泵机组通常装在室外或者半开放空间,环境湿度大。PCB板裸露使用,几个月就容易出问题。建议买一个ABS防水接线盒,把控制板、继电器模块和电源模块全部装进去,进出线口用防水接头锁紧。
内部走线也要注意强弱电分离,220V线束和信号线保持3厘米以上的距离,交叉处用扎带固定避免摩擦。我实测下来,做好物理隔离后,系统的故障率下降了一半以上。
另一个容易忽略的点是看门狗。ESP8266在异常情况下会死机,比如WiFi协议栈卡死或者堆内存耗尽。代码里我加了一段硬件看门狗喂狗逻辑:
ESP.wdtDisable(); ESP.wdtEnable(3000);主循环里每次循环至少执行一次ESP.wdtFeed(),这样即使某段代码阻塞超过3秒,系统也会自动重启恢复。这个机制在远程无人值守场景下是保命功能,绝对要加上。
5.3 离线状态下的控制策略优化
如果家里WiFi断了或者MQTT服务器挂了,系统就完全失控了吗?不会。我的代码里设计了一个降级模式:当WiFi重连超过15秒还没成功,就切换到纯本地逻辑模式。在这个模式下,按键调温度和回差控制依然完全独立运行,只是不更新远程状态。
这个设计思路很重要。智能硬件的本质是以前的嵌入式系统加上了网络能力,但网络只是辅助手段,核心控制逻辑绝不能依赖网络。网络恢复后,系统会自动重连MQTT并补发一条最新的状态快照,保证云端和本地状态最终一致。
我自己在实际使用中有个体会:这套系统最爽的不是远程开关,而是回家前用手机把水温从40℃调高到55℃,到家马上能洗个热水澡。当时做这个项目的初衷就是为了这个场景,后期加的按键、屏幕等,都是为了让家里的老人不必依赖手机也能操作。后来有一次我给朋友装了第二套,发现在断电重启后,原设定值会被固定值覆盖,查了半天,原来是EEPROM参数在初始化时被重新写入固件默认值。这个坑我写了很长的注释放在config.h里,提醒看代码的兄弟注意。
如果你也想复刻这个项目,我建议第一步先把硬件按接线图搭好,烧录一个最简单的Blink样例确认开发板硬件没问题,再烧录完整代码。如果直接烧录全套代码出了问题,排查范围会比较广,反而容易劝退。整个过程大概需要一个下午,做成功后你会发现,智能家居其实没有想象中那么神秘,核心不过就是传感器加控制逻辑加网络通信这三件套。
本文还有配套的精品资源,点击获取