三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于ESP32与Home Assistant的智能喂奶计时器:从硬件连接到米家同步

基于ESP32与Home Assistant的智能喂奶计时器:从硬件连接到米家同步

最近在折腾智能家居,想给家里的喂奶记录做个自动化方案。网上方案要么太复杂,要么需要额外买设备。正好手头有闲置的ESP32和一块墨水屏,就琢磨着能不能自己做一个。核心需求很简单:每次喂奶,按一下按钮,屏幕开始计时并显示,同时把这次喂奶事件同步到米家App里,方便全家查看记录。

折腾了一圈,发现单纯用ESP32直连米家比较麻烦,而Home Assistant(HA)作为智能家居中枢,既能轻松接入ESP32设备,又能通过插件将数据同步到米家。于是,一个基于ESP32 + 墨水屏 + Home Assistant + 米家的喂奶计时器方案就诞生了。本文将手把手带你复现这个项目,从硬件连接、固件开发、HA配置到米家接入,完整走一遍。即使你是物联网开发新手,跟着步骤也能做出来。

1. 项目核心思路与硬件准备

1.1 项目架构与工作流程

整个项目的核心思路是:ESP32作为数据采集和显示终端,Home Assistant作为智能中枢和协议桥梁,米家作为最终的用户交互界面。

具体工作流程如下:

  1. 触发:家长按下连接在ESP32上的物理按钮。
  2. 记录与显示:ESP32记录按下按钮的时间戳,并在本地的墨水屏上显示本次喂奶的开始时间、距离上次喂奶的间隔,并启动一个计时器显示本次喂奶已持续时长。
  3. 上报数据:ESP32通过Wi-Fi将这次喂奶事件(包含时间戳、事件类型等)发送到家庭内网的Home Assistant服务器。
  4. 中枢处理:Home Assistant接收到数据,更新其内部的传感器状态。我们可以在这里设置自动化,比如喂奶超过30分钟发出提醒。
  5. 同步至米家:利用Home Assistant的第三方集成(如Xiaomi Miot Auto),将Home Assistant里的这个“喂奶计时器传感器”同步到米家App中。
  6. 远程查看:家人打开米家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开发板支持

  1. 从Arduino官网下载并安装最新版Arduino IDE。
  2. 打开IDE,点击文件->首选项。在“附加开发板管理器网址”中,填入以下地址:
    https://espressif.github.io/arduino-esp32/package_esp32_index.json
    如果有其他网址,用逗号隔开。
  3. 点击工具->开发板->开发板管理器。搜索“esp32”,找到由Espressif Systems提供的“ESP32”开发板包,点击安装。安装过程可能需要一些时间。

2.2 安装必要的库

本项目需要两个核心库:

  1. GxEPD2:用于驱动墨水屏。这是目前对ESP32支持较好的墨水屏库。
  2. 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界面上创建:

  1. 进入HA侧边栏配置->设备与服务->辅助元素
  2. 点击右下角创建辅助元素
  3. 选择传感器
  4. 名称填Baby Feeding Timer,实体ID会自动生成,如sensor.baby_feeding_timer。单位可以留空或填“状态”。
  5. 创建后,记下这个实体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时,生成一条永久的喂奶记录。

  1. 进入配置->自动化与场景->创建自动化
  2. 选择“从空自动化开始”。
  3. 触发器:选择“状态”,实体选择你的喂奶传感器(如sensor.baby_feeding),从*变为feeding
  4. 动作:选择“调用服务”。
  5. 服务选择: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集成

  1. 在HA的配置->设备与服务->集成页面,点击右下角“添加集成”。
  2. 搜索“Xiaomi Miot Auto”并安装。
  3. 安装后,按照提示登录你的小米账号(不建议使用主账号,可新建子账号或使用扫码登录)。
  4. 集成会自动拉取你米家账号下的设备。但我们需要的是将HA的设备暴露给米家。

5.2 将HA传感器暴露为米家设备

Xiaomi Miot Auto集成本身主要用来接入米家设备到HA。要将HA实体暴露给米家,通常需要借助另一个集成,如“Home Assistant Cloud”(需付费订阅)或使用“Node-RED”配合“miot”节点等更复杂的方式。

这里介绍一个通过“智能音箱”间接实现的实用方法:

  1. 在HA中,确保你的喂奶传感器实体(sensor.baby_feeding)和记录时间的input_datetime实体是存在的。
  2. 在HA中安装并配置“Google Assistant”“Amazon Alexa”集成(这通常需要一些网络条件,本文不展开)。
  3. 在这些语音助手集成中,将上述实体“暴露”给语音助手。
  4. 在米家App中,绑定同一个小米账号的“小爱音箱”技能。当小爱音箱接入了Google Assistant或Alexa后,理论上可以通过语音查询这些由HA暴露出来的设备状态。
  5. 更直接的方法是,如果你有一台小爱音箱Pro(带蓝牙Mesh网关),有些教程可以通过本地局域网协议,将HA的虚拟设备同步过去,但这需要较深的折腾。

更稳定简单的替代方案(推荐):使用Telegram Bot或AppDaemon创建通知如果米家接入太复杂,一个极佳的替代方案是使用HA的Telegram Bot集成:

  1. 在HA中安装“Telegram Bot”集成。
  2. 在Telegram中找@BotFather创建一个机器人,获取token。
  3. 在HA配置中填入token,并获取你的Chat ID。
  4. 创建一个自动化:当喂奶事件触发时,调用telegram_bot.send_message服务,将“宝宝于XX时间开始吃奶了”这样的消息发送到你的Telegram。
  5. 家人全部安装Telegram,加入同一个群组,即可实时收到推送。这比在米家App里找设备更直观及时。

6. 常见问题与排查

在开发过程中,你可能会遇到以下问题:

问题现象可能原因排查思路
ESP32无法连接Wi-FiSSID/密码错误、路由器设置(如MAC过滤)、信号弱检查代码中的SSID/密码;用手机确认网络;查看串口日志。
墨水屏不显示或花屏引脚连接错误、库型号不匹配、供电不足逐一检查接线;在GxEPD2_Example中测试你的屏幕型号;尝试外接5V供电。
HTTP请求失败,返回401HA长期令牌错误、令牌未生效、URL错误检查haToken是否填写正确;在HA中确认令牌已创建;检查URL中的IP、端口和实体ID。
HTTP请求失败,返回404HA中对应的实体不存在在HA开发者工具中检查实体ID是否存在;或先在HA界面创建辅助传感器。
按键按下无反应引脚接触不良、内部上拉未启用、按键损坏digitalRead读取引脚状态并打印到串口,确认按下时电平变化;检查接线。
屏幕刷新太慢墨水屏全刷特性、刷新代码逻辑问题确保使用display.display(false)局部刷新;只在必要内容变化时调用updateDisplay()
Home Assistant中看不到状态更新防火墙阻止、ESP32与HA不在同一网段确保ESP32能ping通HA服务器IP;检查HA配置中的http:模块是否启用且未限制内网IP。

7. 项目优化与扩展建议

一个基础版本完成后,可以考虑以下优化,让项目更实用:

  1. 双按键设计:一个键用于“开始喂奶”,另一个键用于“结束喂奶/换边”,这样可以更精确记录单次喂奶时长。
  2. 数据持久化:使用ESP32的Preferences库或LittleFS文件系统,将lastFeedTime等关键数据保存到闪存中。这样即使断电重启,历史记录也不会丢失。
  3. 低功耗优化:喂奶间隙,ESP32可以进入深度睡眠(Deep Sleep),仅由按键中断唤醒。这需要硬件上连接按键到ESP32的RTC唤醒引脚(如GPIO 33)。
  4. 显示优化
    • 增加喂奶历史记录列表(最近5次)。
    • 显示今日喂奶总次数和总时长。
    • 使用更美观的字体和布局。
  5. HA自动化增强
    • 创建“喂奶统计”传感器,计算今日、本周的喂奶频率。
    • 设置提醒自动化:如果距离上次喂奶超过3小时,通过TTS在小爱音箱上播放提醒。
    • 将喂奶数据记录到InfluxDB或MySQL数据库,用于长期分析和可视化(如Grafana面板)。
  6. 外壳与电源:设计并3D打印一个外壳,将ESP32、屏幕、按键集成进去,并使用锂电池供电,使其成为一个独立的便携设备。

这个项目完美结合了嵌入式硬件(ESP32)、本地智能中枢(Home Assistant)和消费级物联网平台(米家)的优点。它不仅解决了喂奶记录这个具体问题,更提供了一个模板:如何利用开源硬件和软件,桥接个人定制化需求与成熟商业生态。你可以将此模式扩展到其他家庭数据记录场景,如宠物喂食、植物浇水、药品提醒等。

← 返回列表