基于ESP32与Home Assistant的智能喂奶计时器:从硬件连接到米家同步
最近在折腾智能家居,想给家里的喂奶记录做个自动化方案。网上方案要么太复杂,要么需要额外买设备。正好手头有闲置的ESP32和一块墨水屏,就琢磨着能不能自己做一个。核心需求很简单:每次喂奶,按一下按钮,屏幕开始计时并显示,同时把这次喂奶事件同步到米家App里,方便全家查看记录。
折腾了一圈,发现单纯用ESP32直连米家比较麻烦,而Home Assistant(HA)作为智能家居中枢,既能轻松接入ESP32设备,又能通过插件将数据同步到米家。于是,一个基于ESP32 + 墨水屏 + Home Assistant + 米家的喂奶计时器方案就诞生了。本文将手把手带你复现这个项目,从硬件连接、固件开发、HA配置到米家接入,完整走一遍。即使你是物联网开发新手,跟着步骤也能做出来。
1. 项目核心思路与硬件准备
1.1 项目架构与工作流程
整个项目的核心思路是:ESP32作为数据采集和显示终端,Home Assistant作为智能中枢和协议桥梁,米家作为最终的用户交互界面。
具体工作流程如下:
- 触发:家长按下连接在ESP32上的物理按钮。
- 记录与显示:ESP32记录按下按钮的时间戳,并在本地的墨水屏上显示本次喂奶的开始时间、距离上次喂奶的间隔,并启动一个计时器显示本次喂奶已持续时长。
- 上报数据:ESP32通过Wi-Fi将这次喂奶事件(包含时间戳、事件类型等)发送到家庭内网的Home Assistant服务器。
- 中枢处理:Home Assistant接收到数据,更新其内部的传感器状态。我们可以在这里设置自动化,比如喂奶超过30分钟发出提醒。
- 同步至米家:利用Home Assistant的第三方集成(如Xiaomi Miot Auto),将Home Assistant里的这个“喂奶计时器传感器”同步到米家App中。
- 远程查看:家人打开米家App,即可在设备列表里看到喂奶记录和当前状态,实现信息共享。
这个架构的优势在于,ESP32只需要实现简单的数据上报和显示,复杂的集成、自动化、远程访问都由Home Assistant承担,大大降低了嵌入式端的开发难度。
1.2 硬件清单与连接
你需要准备以下硬件:
| 组件 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| 主控芯片 | ESP32开发板(如ESP32-WROOM-32) | 1 | 核心控制器,负责逻辑、联网和驱动。 |
| 显示屏 | 2.9英寸或4.2英寸三色墨水屏(带SPI接口) | 1 | 用于低功耗显示信息。推荐使用GxEPD2库支持的型号,如GDEW029T5。 |
| 输入设备 | 轻触按键开关 | 1-2 | 用于开始计时、重置等操作。 |
| 电源 | USB数据线或3.7V锂电池 | 1 | 供电。如果长期使用,建议接充电宝或5V电源。 |
| 连接线 | 杜邦线(母对母、公对母) | 若干 | 用于连接各组件。 |
| 电阻 | 10kΩ 电阻 | 1-2 | 按键的上拉/下拉电阻。 |
接线示意图(以ESP32 DevKit V1为例):
墨水屏(以GDEW029T5为例)通常需要连接以下引脚:
- VCC-> ESP32的3.3V
- GND-> ESP32的GND
- DIN/MOSI-> ESP32的GPIO 23
- CLK/SCK-> ESP32的GPIO 18
- CS/SS-> ESP32的GPIO 5
- DC-> ESP32的GPIO 17
- RST-> ESP32的GPIO 16
- BUSY-> ESP32的GPIO 4
注意:不同型号的墨水屏引脚定义可能不同,请务必查阅你的屏幕资料手册。
按键连接(以使用一个按键为例):
- 按键一端接ESP32的GPIO 13(可自定义)。
- 按键另一端接GND。
- 在GPIO 13和3.3V之间连接一个10kΩ上拉电阻。这样,按键未按下时引脚读数为高电平(1),按下时为低电平(0)。
2. 软件开发环境搭建
我们将使用Arduino IDE来开发ESP32的固件,因为它对新手友好,库生态丰富。
2.1 安装Arduino IDE与ESP32开发板支持
- 从Arduino官网下载并安装最新版Arduino IDE。
- 打开IDE,点击
文件->首选项。在“附加开发板管理器网址”中,填入以下地址:
如果有其他网址,用逗号隔开。https://espressif.github.io/arduino-esp32/package_esp32_index.json - 点击
工具->开发板->开发板管理器。搜索“esp32”,找到由Espressif Systems提供的“ESP32”开发板包,点击安装。安装过程可能需要一些时间。
2.2 安装必要的库
本项目需要两个核心库:
- GxEPD2:用于驱动墨水屏。这是目前对ESP32支持较好的墨水屏库。
- ArduinoJson:用于处理与Home Assistant通信的JSON数据。
安装方法:点击项目->加载库->管理库...,在库管理器中分别搜索“GxEPD2”和“ArduinoJson”并安装。
2.3 准备Home Assistant环境
你需要一个正在运行的Home Assistant。安装方式有多种:
- 推荐(最简单):在树莓派、旧电脑或虚拟机上安装Home Assistant OS。
- Docker安装:适合有一定Linux和Docker经验的用户。
- 直接安装:在Linux系统上通过Python虚拟环境安装。
确保你的Home Assistant可以正常访问,并且ESP32所在的网络能够连接到HA服务器的IP地址(通常是你的家庭路由器IP段,如192.168.1.x)。
3. ESP32固件开发详解
固件的核心功能:连接Wi-Fi、监听按键、更新屏幕、向Home Assistant发送数据。
3.1 创建新项目与基础配置
在Arduino IDE中新建一个项目。首先,包含必要的头文件和定义全局变量、常量。
// 喂奶计时器 - ESP32 with E-Ink & Home Assistant #include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> #include <GxEPD2_BW.h> // 我们使用黑白驱动,如果你的屏是三色,用GxEPD2_3C #include <Fonts/FreeMonoBold12pt7b.h> // 选择一种字体 // ==== 1. 网络配置 ==== const char* ssid = "你的Wi-Fi名称"; const char* password = "你的Wi-Fi密码"; const char* haServer = "http://192.168.1.100:8123"; // 你的HA地址和端口 const char* haToken = "你的HA长期访问令牌"; // 在HA侧边栏“配置文件”最下方创建 // ==== 2. 硬件引脚定义 ==== #define BUTTON_PIN 13 // 按键连接的GPIO #define EINK_CS 5 #define EINK_DC 17 #define EINK_RST 16 #define EINK_BUSY 4 // ==== 3. 墨水屏对象定义 ==== // 根据你的屏幕型号修改!以下是2.9英寸黑白屏的示例 GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(EINK_CS, EINK_DC, EINK_RST, EINK_BUSY)); // ==== 4. 全局变量 ==== unsigned long lastFeedTime = 0; // 上次喂奶的时间戳(毫秒) unsigned long currentFeedStartTime = 0; // 本次喂奶开始时间戳 bool isFeeding = false; // 当前是否正在喂奶 String lastInterval = "--"; // 上次喂奶间隔显示文本 int buttonState = HIGH; int lastButtonState = HIGH; void setup() { Serial.begin(115200); pinMode(BUTTON_PIN, INPUT_PULLUP); // 使用内部上拉电阻,按键另一端接GND initDisplay(); connectToWiFi(); // 尝试从HA获取上次记录的时间(可选,进阶功能) // fetchLastFeedTimeFromHA(); } void loop() { checkButton(); updateDisplay(); // 如果正在喂奶,每10秒更新一次计时显示 if (isFeeding && (millis() - currentFeedStartTime) % 10000 < 50) { updateDisplay(); } delay(50); // 简单的防抖和降低CPU占用 }3.2 核心功能函数实现
接下来,我们逐一实现setup中调用的函数。
初始化显示屏:
void initDisplay() { display.init(115200, true, 2, false); // 初始化,使用SPI 10MHz,重置 display.setRotation(1); // 根据你的屏幕方向调整,0-3 display.setFont(&FreeMonoBold12pt7b); display.setTextColor(GxEPD_BLACK); display.fillScreen(GxEPD_WHITE); displayDisplay(); // 清屏为白色 } void displayDisplay() { display.display(false); // false表示局部刷新(更快),true全刷(更干净) }连接Wi-Fi:
void connectToWiFi() { Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); }按键检测与逻辑处理:这是核心逻辑,按下按键表示一次喂奶事件开始或结束(本项目简化为按下即开始一次新记录,并结束上一次)。
void checkButton() { buttonState = digitalRead(BUTTON_PIN); // 检测下降沿(按键按下) if (buttonState == LOW && lastButtonState == HIGH) { delay(50); // 简单防抖 if (digitalRead(BUTTON_PIN) == LOW) { // 确认按下 onFeedButtonPressed(); } } lastButtonState = buttonState; } void onFeedButtonPressed() { Serial.println("Feed button pressed!"); unsigned long now = millis(); // 计算与上次喂奶的间隔(分钟) if (lastFeedTime > 0) { unsigned long interval = (now - lastFeedTime) / 60000; // 转换为分钟 lastInterval = String(interval) + " min"; } else { lastInterval = "First"; } // 更新喂奶时间记录 lastFeedTime = now; currentFeedStartTime = now; isFeeding = true; // 1. 更新本地屏幕 updateDisplay(); // 2. 发送事件到Home Assistant sendFeedEventToHA(now); // 3. (可选)蜂鸣器提示或LED闪烁 }更新墨水屏显示:墨水屏刷新慢,要优化显示逻辑,尽量减少全屏刷新。
void updateDisplay() { display.setPartialWindow(0, 0, display.width(), display.height()); // 设置局部刷新区域(这里实际是全屏) display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(10, 30); display.print("Baby Feeding Timer"); display.setCursor(10, 70); display.print("Last Interval:"); display.setCursor(180, 70); display.print(lastInterval); display.setCursor(10, 110); display.print("Status:"); display.setCursor(100, 110); if (isFeeding) { display.print("FEEDING"); // 计算并显示本次喂奶持续时间 unsigned long feedingDuration = (millis() - currentFeedStartTime) / 1000; // 秒 int minutes = feedingDuration / 60; int seconds = feedingDuration % 60; display.setCursor(10, 150); display.print("Duration: "); display.print(minutes); display.print("m "); display.print(seconds); display.print("s"); } else { display.print("READY"); display.setCursor(10, 150); display.print("Press to start"); } // 显示上次喂奶时间 display.setCursor(10, 190); display.print("Last Feed:"); if (lastFeedTime > 0) { // 这里可以添加时间格式转换函数,将时间戳转为可读时间 display.setCursor(130, 190); display.print(formatTime(lastFeedTime)); } } while (display.nextPage()); } // 一个简单的时间格式化函数(示例,未处理日期) String formatTime(unsigned long timestamp) { unsigned long seconds = timestamp / 1000; int hours = (seconds % 86400L) / 3600; int minutes = (seconds % 3600) / 60; char buffer[10]; sprintf(buffer, "%02d:%02d", hours, minutes); return String(buffer); }与Home Assistant通信(核心):我们将通过Home Assistant的RESTful API来创建一个传感器并更新其状态。 首先,需要在HA中手动创建一个“辅助元素”传感器,或者通过API自动创建。这里我们采用通过API发送状态更新的方式,假设传感器已经创建好。
void sendFeedEventToHA(unsigned long eventTime) { if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi not connected!"); return; } HTTPClient http; String url = String(haServer) + "/api/states/sensor.baby_feeding"; // `sensor.baby_feeding` 是你的实体ID // 构建JSON数据 DynamicJsonDocument doc(512); doc["state"] = "feeding"; // 状态可以是 "feeding", "ready"等 JsonObject attributes = doc.createNestedObject("attributes"); attributes["last_feed_time"] = eventTime; // 时间戳 attributes["last_interval"] = lastInterval; // 间隔文本 attributes["friendly_name"] = "Baby Feeding Timer"; // 友好名称 String jsonString; serializeJson(doc, jsonString); http.begin(url); http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", "Bearer " + String(haToken)); // 认证 int httpCode = http.PUT(jsonString); // 使用PUT方法更新状态 if (httpCode > 0) { String payload = http.getString(); Serial.printf("[HTTP] PUT... code: %d\n", httpCode); Serial.println(payload); } else { Serial.printf("[HTTP] PUT... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); }在Home Assistant中创建传感器(备用方法):如果你不想在代码中动态创建,可以提前在HA的configuration.yaml文件中添加一个模板传感器,或者更简单,在HA界面上创建:
- 进入HA侧边栏
配置->设备与服务->辅助元素。 - 点击右下角
创建辅助元素。 - 选择
传感器。 - 名称填
Baby Feeding Timer,实体ID会自动生成,如sensor.baby_feeding_timer。单位可以留空或填“状态”。 - 创建后,记下这个实体ID,并更新上面代码中的URL(
/api/states/sensor.baby_feeding_timer)。
4. Home Assistant配置与自动化
ESP32端完成后,我们需要在Home Assistant中配置,使其能接收数据并同步到米家。
4.1 获取长期访问令牌
在HA网页界面,点击左下角你的用户名,滚动到最下方,找到“长期访问令牌”,点击“创建令牌”,为其命名(如“ESP32_Feeding_Timer”),然后复制生成的令牌字符串。此令牌只显示一次,请妥善保存,并填入ESP32代码的haToken变量中。
4.2 验证ESP32通信
将完整的Arduino代码编译上传到ESP32。按下按键后,查看串口监视器(波特率115200)。你应该能看到连接Wi-Fi成功的日志,以及发送HTTP请求到HA的日志。 在HA的侧边栏,点击开发者工具->状态。在实体输入框中,输入你定义的实体ID(如sensor.baby_feeding)。当你按下ESP32的按键后,刷新或监听此实体,应该能看到其状态从unknown变为feeding,并且属性中包含了时间戳等信息。
4.3 创建喂奶记录自动化(可选)
我们可以利用HA的自动化功能,当传感器状态变为feeding时,生成一条永久的喂奶记录。
- 进入
配置->自动化与场景->创建自动化。 - 选择“从空自动化开始”。
- 触发器:选择“状态”,实体选择你的喂奶传感器(如
sensor.baby_feeding),从*变为feeding。 - 动作:选择“调用服务”。
- 服务选择:
input_datetime.set_datetime。这是一个用于记录时间的辅助元素。- 首先,你需要创建一个
input_datetime辅助元素,命名为“Last Feed DateTime”。 - 在动作配置中,目标实体选择这个
input_datetime。 - 在“服务数据”中填入:
{ "timestamp": "{{ now().timestamp() }}" }
input_datetime都会更新为当前时间,这个时间会被永久记录,即使ESP32重启也不会丢失。 - 首先,你需要创建一个
5. 接入米家App
这是实现远程查看的关键。由于米家生态相对封闭,我们通过Home Assistant的第三方集成“Xiaomi Miot Auto”来反向接入。
5.1 安装Xiaomi Miot Auto集成
- 在HA的
配置->设备与服务->集成页面,点击右下角“添加集成”。 - 搜索“Xiaomi Miot Auto”并安装。
- 安装后,按照提示登录你的小米账号(不建议使用主账号,可新建子账号或使用扫码登录)。
- 集成会自动拉取你米家账号下的设备。但我们需要的是将HA的设备暴露给米家。
5.2 将HA传感器暴露为米家设备
Xiaomi Miot Auto集成本身主要用来接入米家设备到HA。要将HA实体暴露给米家,通常需要借助另一个集成,如“Home Assistant Cloud”(需付费订阅)或使用“Node-RED”配合“miot”节点等更复杂的方式。
这里介绍一个通过“智能音箱”间接实现的实用方法:
- 在HA中,确保你的喂奶传感器实体(
sensor.baby_feeding)和记录时间的input_datetime实体是存在的。 - 在HA中安装并配置“Google Assistant”或“Amazon Alexa”集成(这通常需要一些网络条件,本文不展开)。
- 在这些语音助手集成中,将上述实体“暴露”给语音助手。
- 在米家App中,绑定同一个小米账号的“小爱音箱”技能。当小爱音箱接入了Google Assistant或Alexa后,理论上可以通过语音查询这些由HA暴露出来的设备状态。
- 更直接的方法是,如果你有一台小爱音箱Pro(带蓝牙Mesh网关),有些教程可以通过本地局域网协议,将HA的虚拟设备同步过去,但这需要较深的折腾。
更稳定简单的替代方案(推荐):使用Telegram Bot或AppDaemon创建通知如果米家接入太复杂,一个极佳的替代方案是使用HA的Telegram Bot集成:
- 在HA中安装“Telegram Bot”集成。
- 在Telegram中找
@BotFather创建一个机器人,获取token。 - 在HA配置中填入token,并获取你的Chat ID。
- 创建一个自动化:当喂奶事件触发时,调用
telegram_bot.send_message服务,将“宝宝于XX时间开始吃奶了”这样的消息发送到你的Telegram。 - 家人全部安装Telegram,加入同一个群组,即可实时收到推送。这比在米家App里找设备更直观及时。
6. 常见问题与排查
在开发过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| ESP32无法连接Wi-Fi | SSID/密码错误、路由器设置(如MAC过滤)、信号弱 | 检查代码中的SSID/密码;用手机确认网络;查看串口日志。 |
| 墨水屏不显示或花屏 | 引脚连接错误、库型号不匹配、供电不足 | 逐一检查接线;在GxEPD2_Example中测试你的屏幕型号;尝试外接5V供电。 |
| HTTP请求失败,返回401 | HA长期令牌错误、令牌未生效、URL错误 | 检查haToken是否填写正确;在HA中确认令牌已创建;检查URL中的IP、端口和实体ID。 |
| HTTP请求失败,返回404 | HA中对应的实体不存在 | 在HA开发者工具中检查实体ID是否存在;或先在HA界面创建辅助传感器。 |
| 按键按下无反应 | 引脚接触不良、内部上拉未启用、按键损坏 | 用digitalRead读取引脚状态并打印到串口,确认按下时电平变化;检查接线。 |
| 屏幕刷新太慢 | 墨水屏全刷特性、刷新代码逻辑问题 | 确保使用display.display(false)局部刷新;只在必要内容变化时调用updateDisplay()。 |
| Home Assistant中看不到状态更新 | 防火墙阻止、ESP32与HA不在同一网段 | 确保ESP32能ping通HA服务器IP;检查HA配置中的http:模块是否启用且未限制内网IP。 |
7. 项目优化与扩展建议
一个基础版本完成后,可以考虑以下优化,让项目更实用:
- 双按键设计:一个键用于“开始喂奶”,另一个键用于“结束喂奶/换边”,这样可以更精确记录单次喂奶时长。
- 数据持久化:使用ESP32的Preferences库或LittleFS文件系统,将
lastFeedTime等关键数据保存到闪存中。这样即使断电重启,历史记录也不会丢失。 - 低功耗优化:喂奶间隙,ESP32可以进入深度睡眠(Deep Sleep),仅由按键中断唤醒。这需要硬件上连接按键到ESP32的RTC唤醒引脚(如GPIO 33)。
- 显示优化:
- 增加喂奶历史记录列表(最近5次)。
- 显示今日喂奶总次数和总时长。
- 使用更美观的字体和布局。
- HA自动化增强:
- 创建“喂奶统计”传感器,计算今日、本周的喂奶频率。
- 设置提醒自动化:如果距离上次喂奶超过3小时,通过TTS在小爱音箱上播放提醒。
- 将喂奶数据记录到InfluxDB或MySQL数据库,用于长期分析和可视化(如Grafana面板)。
- 外壳与电源:设计并3D打印一个外壳,将ESP32、屏幕、按键集成进去,并使用锂电池供电,使其成为一个独立的便携设备。
这个项目完美结合了嵌入式硬件(ESP32)、本地智能中枢(Home Assistant)和消费级物联网平台(米家)的优点。它不仅解决了喂奶记录这个具体问题,更提供了一个模板:如何利用开源硬件和软件,桥接个人定制化需求与成熟商业生态。你可以将此模式扩展到其他家庭数据记录场景,如宠物喂食、植物浇水、药品提醒等。
