基于ESP32与Home Assistant的智能喂奶计时器开发全攻略
最近在折腾智能家居想给家里的喂奶记录做个自动化方案。市面上现成的喂奶计时器要么功能单一要么需要频繁操作手机对于半夜迷迷糊糊起来喂奶的场景实在不够友好。于是萌生了自己动手做一个的想法核心需求是一键记录喂奶开始/结束时间并能通过家里的米家生态查看历史记录和统计数据。经过一番选型和折腾最终确定了以ESP32作为主控搭配一块低功耗墨水屏作为本地显示再通过Home Assistant将数据接入米家的完整方案。这个方案的好处很明显ESP32性能足够、联网方便墨水屏常亮显示不刺眼且超级省电Home Assistant作为强大的智能家居中枢能轻松实现与米家设备的联动和复杂自动化。本文将手把手带你从零开始完成这个“喂奶计时器”从硬件连接、固件开发、Home Assistant配置到最终接入米家的全流程。即使你是物联网开发的新手跟着步骤走也能成功复现。文章会涵盖ESP32开发环境搭建、墨水屏驱动、HTTP服务器搭建、Home Assistant RESTful传感器创建以及米家通知推送等核心环节并提供完整的、可复制的代码。1. 项目核心设计与硬件准备在开始写代码之前我们需要明确整个系统的工作流程和所需的硬件组件。1.1 系统工作流程用户交互通过物理按钮或触摸触发ESP32开始或停止计时。本地处理与显示ESP32记录时间戳计算本次喂奶时长并实时更新墨水屏上的信息如开始时间、当前时长、历史统计。数据上报ESP32内置一个简单的HTTP服务器。当喂奶状态变化开始/结束时通过HTTP POST请求将JSON格式的数据发送到家庭内网中的Home Assistant服务器。中枢处理与存储Home Assistant接收到数据后将其更新到对应的传感器实体中。这些传感器的历史数据会被Home Assistant自动记录。生态接入与通知在Home Assistant中我们可以利用“米家通知”或“Xiaomi Miot Auto”等集成将喂奶事件或统计信息推送到米家App或者与其他米家设备联动例如喂奶开始时自动调暗灯光。1.2 硬件清单与连接以下是本项目所需的核心硬件及简要说明组件型号/规格说明参考链接主控板ESP32开发板如ESP32-DevKitC核心处理器负责逻辑控制、联网和驱动屏幕。常见ESP32开发板均可显示屏2.9英寸或4.2英寸电子墨水屏e-Paper用于显示信息功耗极低刷新后无需供电即可保持显示。例如GDEH029A12.9寸按键轻触开关 x2一个用于“开始/记录”一个用于“重置/模式切换”。电源USB供电或3.7V锂电池开发阶段可用USB成品可考虑电池供电。接线示意图以2.9寸SPI接口墨水屏为例墨水屏与ESP32通常通过SPI接口通信按键连接至GPIO引脚并启用内部上拉电阻。墨水屏 - ESP32 GPIO VCC - 3.3V GND - GND DIN (MOSI) - GPIO 23 CLK (SCLK) - GPIO 18 CS - GPIO 5 DC - GPIO 17 RST - GPIO 16 BUSY - GPIO 4 (可选用于等待屏幕刷新完成) 按键1开始 - GPIO 13 (另一端接GND) 按键2重置 - GPIO 12 (另一端接GND)重要提示不同型号的墨水屏引脚定义可能不同请务必以你购买的屏幕规格书为准。VCC必须接3.3V接5V可能会损坏屏幕。2. 软件开发环境搭建我们将使用Arduino IDE进行ESP32的开发因为它库管理方便社区支持好。2.1 安装Arduino IDE与ESP32开发板支持下载Arduino IDE从 Arduino官网 下载并安装最新版。添加ESP32开发板网址打开Arduino IDE点击文件-首选项。在“附加开发板管理器网址”中输入https://espressif.github.io/arduino-esp32/package_esp32_index.json如果有其他网址用逗号隔开。安装ESP32开发板点击工具-开发板-开发板管理器。搜索“esp32”找到由“Espressif Systems”提供的“ESP32 Arduino”并安装。2.2 安装必要的库本项目需要以下库均可以通过Arduino IDE的库管理器安装。GxEPD2强大的电子墨水屏驱动库支持众多型号。工具-管理库...- 搜索“GxEPD2”安装由“Jean-Marc Zingg”提供的版本。ArduinoJson用于生成和解析JSON数据与Home Assistant通信必备。搜索“ArduinoJson”安装由“Benoît Blanchon”提供的版本版本6.x或7.x均可代码需对应调整。WiFi和HTTPClientESP32内置无需额外安装。安装完成后你的Arduino IDE库管理中应该能看到这些库。3. 核心代码实现与解析我们将代码分为几个部分WiFi连接、墨水屏驱动、HTTP服务器、喂奶逻辑主循环。3.1 基础配置与头文件首先创建一个新的Arduino项目并包含必要的头文件和定义常量。// 喂奶计时器 - ESP32 墨水屏 HomeAssistant #include WiFi.h #include WebServer.h // 用于创建简单的HTTP服务器接收指令可选或仅用于调试 #include HTTPClient.h // 用于向Home Assistant发送数据 #include ArduinoJson.h // 使用版本6.x // 墨水屏驱动 - 根据你的屏幕型号修改 #include GxEPD2_BW.h // 黑白屏驱动 #include Fonts/FreeMonoBold9pt7b.h // 字体 // 2.9寸屏幕定义例如GDEH029A1 GxEPD2_BWGxEPD2_290, GxEPD2_290::HEIGHT display(GxEPD2_290(/*CS*/5, /*DC*/17, /*RST*/16, /*BUSY*/4)); // WiFi配置 const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; // Home Assistant配置 const char* haServer http://你的HA内网IP:8123; // Home Assistant地址 const char* haToken 你的长期访问令牌; // 在HA侧边栏用户资料中创建 String entityId sensor.feeding_timer; // 传感器实体ID // 引脚定义 const int buttonStartPin 13; const int buttonResetPin 12; // 喂奶状态变量 bool isFeeding false; unsigned long feedStartTime 0; unsigned long lastDuration 0; // 上一次喂奶时长秒 int feedCountToday 0; unsigned long lastMidnightReset 0; // HTTP服务器用于简单调试或本地控制 WebServer server(80);3.2 初始化设置setup函数在setup()函数中我们需要初始化串口、屏幕、WiFi、引脚和HTTP服务器。void setup() { Serial.begin(115200); delay(1000); // 初始化墨水屏 display.init(115200, true, 2, false); // 使用初始化 display.setRotation(1); // 根据你的安装方向调整旋转 display.setFont(FreeMonoBold9pt7b); display.setTextColor(GxEPD_BLACK); displayFullScreenInfo(Booting...); // 自定义全屏刷新函数 // 连接WiFi connectToWiFi(); // 初始化按键引脚启用内部上拉 pinMode(buttonStartPin, INPUT_PULLUP); pinMode(buttonResetPin, INPUT_PULLUP); // 初始化HTTP服务器用于调试 server.on(/, handleRoot); // 显示状态页面 server.on(/start, handleStart); // 模拟开始喂奶 server.on(/stop, handleStop); // 模拟停止喂奶 server.on(/data, handleData); // 获取当前数据JSON格式 server.begin(); Serial.println(HTTP server started); // 从NVS非易失存储读取上次的状态可选实现持久化 // loadStateFromNVS(); // 更新屏幕初始信息 updateDisplay(); }3.3 WiFi连接函数一个稳定的WiFi连接是数据上报的基础。void connectToWiFi() { Serial.print(Connecting to ); Serial.println(ssid); displayPartialText(10, 30, Connecting WiFi...); // 局部刷新显示状态 WiFi.begin(ssid, password); int attempts 0; while (WiFi.status() ! WL_CONNECTED attempts 20) { delay(500); Serial.print(.); attempts; } Serial.println(); if (WiFi.status() WL_CONNECTED) { Serial.print(Connected! IP address: ); Serial.println(WiFi.localIP()); displayPartialText(10, 50, WiFi OK. IP:); displayPartialText(10, 70, WiFi.localIP().toString().c_str()); } else { Serial.println(WiFi Connection Failed!); displayPartialText(10, 50, WiFi FAILED!); // 可以考虑进入深度睡眠或重启 } delay(2000); }3.4 喂奶逻辑与按钮检测在主循环loop()中我们检测按钮动作并更新状态。void loop() { server.handleClient(); // 处理HTTP服务器请求如果有 // 检查“开始/停止”按钮下降沿触发即按下瞬间 if (digitalRead(buttonStartPin) LOW) { delay(50); // 简单消抖 if (digitalRead(buttonStartPin) LOW) { toggleFeeding(); while(digitalRead(buttonStartPin) LOW); // 等待按键释放 } } // 检查“重置”按钮 if (digitalRead(buttonResetPin) LOW) { delay(50); if (digitalRead(buttonResetPin) LOW) { resetTodayCount(); while(digitalRead(buttonResetPin) LOW); } } // 每天零点重置计数 checkAndResetDailyCounter(); // 如果正在喂奶则更新屏幕上的计时 if (isFeeding) { updateDisplay(); // 此函数内会判断是否需要刷新屏幕避免频繁全刷 } // 短暂延迟降低CPU占用 delay(100); } void toggleFeeding() { if (!isFeeding) { // 开始喂奶 isFeeding true; feedStartTime millis(); Serial.println(Feeding STARTED.); } else { // 结束喂奶 isFeeding false; lastDuration (millis() - feedStartTime) / 1000; // 计算秒数 feedCountToday; Serial.printf(Feeding STOPPED. Duration: %lu seconds.\n, lastDuration); // 关键步骤将本次喂奶数据发送到Home Assistant sendDataToHomeAssistant(); } updateDisplay(); // 状态改变更新屏幕 } void resetTodayCount() { feedCountToday 0; Serial.println(Todays count reset.); updateDisplay(); }3.5 向Home Assistant发送数据核心这是将ESP32数据接入智能家居生态的关键。我们使用Home Assistant的RESTful API来更新一个传感器实体。void sendDataToHomeAssistant() { if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi not connected. Cannot send data.); return; } HTTPClient http; String url String(haServer) /api/states/ entityId; http.begin(url); http.addHeader(Content-Type, application/json); http.addHeader(Authorization, Bearer String(haToken)); // 构建JSON数据 // 参考https://developers.home-assistant.io/docs/api/rest/ DynamicJsonDocument doc(512); doc[state] String(lastDuration); // 传感器状态值这里用时长 doc[attributes][unit_of_measurement] s; doc[attributes][friendly_name] 喂奶计时器; doc[attributes][last_feed_start] feedStartTime / 1000; // 转换为秒时间戳 doc[attributes][feed_count_today] feedCountToday; doc[attributes][is_feeding] isFeeding; String jsonString; serializeJson(doc, jsonString); Serial.println(Sending to HA: jsonString); int httpResponseCode http.PUT(jsonString); // 使用PUT方法更新实体状态 if (httpResponseCode 0) { String response http.getString(); Serial.printf(HA Response code: %d, response: %s\n, httpResponseCode, response.c_str()); } else { Serial.printf(Error sending to HA: %s\n, http.errorToString(httpResponseCode).c_str()); } http.end(); }重要说明haToken需要在Home Assistant中生成。进入HA网页 - 点击左下角用户名 -创建令牌。entityId可以自定义如sensor.feeding_timer。如果实体不存在Home Assistant会在首次收到数据时自动创建。我们使用PUT /api/states/entity_id这个API来创建或更新传感器状态。3.6 墨水屏显示驱动墨水屏刷新较慢需避免频繁全屏刷新。我们采用局部刷新更新数字全屏刷新仅用于重大状态改变。void displayFullScreenInfo(const char* status) { display.setFullWindow(); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(10, 30); display.print(Feeding Timer); display.setCursor(10, 80); display.print(Status:); display.setCursor(100, 80); display.print(status); // 可以绘制更多边框和静态文本 } while (display.nextPage()); } void updateDisplay() { static unsigned long lastPartialUpdate 0; const unsigned long partialUpdateInterval 1000; // 局部刷新间隔1秒 display.setPartialWindow(0, 100, display.width(), 60); // 设置局部刷新区域 if (!isFeeding) { // 非喂奶状态显示上次时长和今日次数 if (millis() - lastPartialUpdate 30000) { // 30秒刷新一次即可 display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.setCursor(10, 120); display.printf(Last: %lu s, lastDuration); display.setCursor(10, 150); display.printf(Today: %d, feedCountToday); display.setCursor(10, 180); display.print(Press to START); } while (display.nextPage()); lastPartialUpdate millis(); } } else { // 喂奶状态动态更新当前时长 if (millis() - lastPartialUpdate partialUpdateInterval) { unsigned long currentDuration (millis() - feedStartTime) / 1000; display.firstPage(); do { display.fillScreen(GxEPD_WHITE); // 在局部窗口内填充白色 display.setCursor(10, 120); display.print(FEEDING...); display.setCursor(10, 150); display.printf(Time: %lu s, currentDuration); } while (display.nextPage()); lastPartialUpdate millis(); } } }4. Home Assistant配置与自动化ESP32端的工作完成后我们需要在Home Assistant中配置传感器和自动化。4.1 创建RESTful传感器方法一configuration.yaml虽然API调用可以自动创建实体但在configuration.yaml中明确定义传感器可以获得更好的控制和自定义属性。打开Home Assistant的configuration.yaml文件添加如下配置# 示例定义喂奶计时器传感器 sensor: - platform: rest name: Feeding Timer resource: !secret feeding_timer_api # 建议将URL放在secrets.yaml scan_interval: 30 # 不主动扫描由ESP32推送 json_attributes: - last_feed_start - feed_count_today - is_feeding value_template: {{ value_json.state }} unit_of_measurement: s device_class: duration然后在secrets.yaml文件中添加feeding_timer_api: http://你的ESP32_IP地址/data注意这种方法要求ESP32提供/data端点返回JSON供HA定期拉取。上文代码中的HTTP服务器提供了此端点。对于低功耗应用更推荐上文使用的**ESP32主动推送PUT**方式。4.2 使用“ESPHome”集成方法二推荐对于ESP32更优雅的方式是使用ESPHome集成。ESPHome可以直接编译固件并自动在HA中创建出高度集成的实体无需手动处理REST API。在HA的HACS商店中安装“ESPHome”集成。在ESPHome仪表盘中添加新设备填写项目名称和ESP32的IP地址。编写ESPHome配置文件feeding_timer.yamlesphome: name: feeding-timer platform: ESP32 board: esp32dev wifi: ssid: !secret wifi_ssid password: !secret wifi_password # 启用AP模式作为备选 ap: ssid: Feeding-Timer Fallback password: fallback密码 captive_portal: # 启用日志和OTA logger: api: ota: # 定义两个按钮传感器 binary_sensor: - platform: gpio pin: GPIO13 name: Feeding Start Button on_press: then: - homeassistant.service: service: input_button.press data: entity_id: input_button.feeding_start - platform: gpio pin: GPIO12 name: Feeding Reset Button on_press: then: - homeassistant.service: service: input_button.press data: entity_id: input_button.feeding_reset # 在HA侧创建辅助元素来记录状态 # 你需要先在HA中创建 input_button 和 input_number 等辅助元素使用ESPHome后固件编写、烧录和HA实体集成会变得非常简单是进阶玩法。4.3 创建喂奶统计自动化无论采用哪种方式当实体状态更新后我们可以在HA中创建自动化。例如创建一个自动化当喂奶结束时sensor.feeding_timer状态从0变为一个正数向手机发送通知并记录到日志。在HA的“自动化”编辑器中使用UI或YAML创建alias: “[喂奶] 记录喂奶结束事件” description: “” trigger: - platform: state entity_id: - sensor.feeding_timer to: “0” from: “0” condition: [] action: - service: persistent_notification.create data: title: 喂奶结束 message: - 本次喂奶时长 {{ states(‘sensor.feeding_timer’) }} 秒。 今日已喂 {{ state_attr(‘sensor.feeding_timer’, ‘feed_count_today’) }} 次。 - service: xiaomi_miot.intelligent_speaker # 示例用小爱音箱播报 data: entity_id: media_player.xiaomi_lx06 text: 宝宝喂奶结束了本次时长{{ states(‘sensor.feeding_timer’) }}秒 mode: single5. 接入米家生态Home Assistant本身不能直接接入米家但可以通过第三方集成实现。5.1 使用“Xiaomi Miot Auto”集成推荐这是目前功能最强大的米家集成之一支持本地和云端模式。在HACS中搜索并安装“Xiaomi Miot Auto”。在HA的“配置”-“集成”中添加“Xiaomi Miot Auto”。按照指引登录你的小米账号或通过令牌获取设备。集成完成后你的米家设备会出现在HA中。5.2 将喂奶事件推送至米家米家App本身没有直接的“传感器”概念可以接收外部数据但我们可以通过变通方式实现通知方法A模拟米家设备状态高级通过“Xiaomi Miot Auto”的“自定义设备”功能或使用ESP32模拟一个蓝牙温湿度计将喂奶时长作为“湿度”或“电量”信息发送在米家App中查看。这种方法较复杂。方法B利用米家通知服务简单有效“Xiaomi Miot Auto”集成提供了向米家App发送通知的服务。在HA的自动化中可以添加如下动作action: - service: xiaomi_miot.send_command data: entity_id: media_player.your_xiaoai_speaker # 你的小爱音箱实体 command: 设备控制 params: did: 你的音箱设备ID siid: 2 # 通常为服务ID aiid: 1 # 通常为“执行文本指令”的动作ID in: “宝宝喂奶结束了时长{{ states(‘sensor.feeding_timer’) }}秒”或者更简单地使用集成提供的notify服务action: - service: notify.xiaomi_miot_手机设备ID data: message: “喂奶结束时长{{ states(‘sensor.feeding_timer’) }}秒”方法C联动米家智能设备这是最实用的方式。例如设置自动化当喂奶开始时自动将卧室的米家智能灯调至暖光1%亮度。action: - service: light.turn_on target: entity_id: light.bedroom_light data: brightness_pct: 1 color_temp: 350这样你就实现了物理设备ESP32计时器- 智能中枢Home Assistant- 用户界面墨水屏、HA仪表盘、米家App通知/设备联动的完整闭环。6. 常见问题与排查思路在制作和调试过程中你可能会遇到以下问题问题现象可能原因排查思路ESP32无法连接WiFiSSID/密码错误、信号弱、路由器设置限制1. 检查串口输出的连接状态。 2. 用手机确认WiFi可连接。 3. 尝试ESP32的WiFi.beginSmartConfig()或AP配网模式。墨水屏不显示或花屏接线错误、电压不足、驱动型号不匹配、刷新过快1. 核对屏幕与ESP32的接线确认VCC接3.3V。 2. 检查GxEPD2库中屏幕型号的定义是否与你的完全一致。 3. 确保代码中display.init()参数正确。 4. 在两次全屏刷新之间增加足够延迟如delay(2000)。HTTP请求失败无法上报HAHA地址/令牌错误、网络不通、防火墙阻止、实体ID权限问题1. 在浏览器访问http://HA_IP:8123/api/测试HA API是否可达。 2. 检查HA长期访问令牌是否正确。 3. 查看HA日志文件home-assistant.log获取更详细的错误信息。 4. 确保ESP32与HA服务器在同一局域网。按钮按下无反应引脚定义错误、内部上拉未启用、按键消抖问题1. 用digitalRead()读取引脚状态并在串口打印确认按下时电平变化。 2. 确认pinMode(pin, INPUT_PULLUP)已设置。 3. 优化消抖逻辑可考虑使用Bounce2库。墨水屏刷新慢有残影这是电子墨水屏的特性1. 尽量减少全屏刷新次数多用局部刷新(setPartialWindow)。 2. 适当降低刷新频率。 3. 可在每次全刷后执行一次display.powerOff()。HA中看不到传感器实体RESTful传感器配置错误、ESP32推送的JSON格式不对、扫描间隔太长1. 使用开发者工具开发者工具-状态搜索sensor.feeding_timer。 2. 使用开发者工具开发者工具-服务调用homeassistant.reload_core_config重载配置。 3. 检查ESP32串口日志确认发送的JSON格式符合HA API要求。7. 优化与扩展思路基础功能实现后可以考虑以下优化方向让项目更实用、更稳定低功耗优化喂奶间隙ESP32可以进入深度睡眠Deep Sleep模式仅由按键或定时器唤醒大幅延长电池续航。墨水屏仅在状态改变时刷新平时不耗电。数据持久化使用ESP32的Preferences库或NVS将feedCountToday等关键数据保存到Flash防止断电丢失。在setup()中读取保存的数据。多用户/多事件支持增加按钮或触摸点区分“左侧喂奶”、“右侧喂奶”或“换尿布”、“睡觉”等不同事件。在HA中创建多个传感器实体分别记录。更美观的UI与图表在Home Assistant中利用History Graph卡片、ApexCharts卡片或Mini Graph卡片绘制每日喂奶次数和时长的趋势图。创建专属的喂奶计时器仪表盘。离线语音提示增加一个蜂鸣器或小型MP3模块在开始、结束或达到设定时长时发出提示音。云端备份与多端同步利用Home Assistant的Google Drive Backup或InfluxDB集成将喂奶数据备份到云端或时序数据库进行长期分析。通过Home Assistant App在任何地方查看数据。这个项目完美结合了硬件DIY、嵌入式开发、网络通信和智能家居集成是一个非常好的物联网全栈实践。它不仅解决了一个具体的家庭需求其技术框架ESP32数据采集 - HTTP/MQTT上报 - Home Assistant处理 - 多端展示可以复用到无数其他场景如环境监测、安防报警、植物浇水等。