💡 阅读提示:本文记录我用压力传感器和ESP8266,给普通水杯加装“智能饮水提醒”功能的完整过程。成本不到30元,一周后每天喝水量从800ml提升到2000ml,真实体验,不吹不黑。
🚨 开篇:那个因为喝水太少进急诊的下午
去年夏天,我在公司连续开了四个小时的会,一口水没喝。散会后站起来,眼前一黑,差点摔倒。同事扶我去医院,医生说:“轻度脱水,血压偏低,回去多喝水。”
“多喝水”三个字听起来像废话,但我是真的记不住。
忙起来的时候,水杯就在手边,但就是想不起来端起来喝一口。一天下来,早上倒的一杯水,下班时还是满的。
我也试过各种办法:手机设闹钟(响了就关,关了就忘)、买了个带时间刻度的水杯(前两格喝完了,后面就没动静了)、甚至贴了便利贴在显示器上(贴了三天就视而不见了)。
一个周末,我看着桌上一块落灰的压力传感器(HX711+称重传感器) ,冒出一个念头:能不能让水杯自己提醒我喝水?
于是有了这个小项目——一个放在水杯底下的“智能杯垫”,实时监测杯子重量变化,算出你已经喝了多少水,定时提醒“该喝水了”,如果杯子空了还会喊你“去接水”。
成本多少?不到30块。
一、我要解决什么?
先把需求想清楚,不做大而全,只解决核心问题。
| 忘了喝水 | 每隔40分钟提醒一次 | 蜂鸣器/微信推送 |
| 不知道自己喝了多少 | 显示今日累计饮水量 | OLED屏幕实时显示 |
| 杯子空了不知道 | 提示“该接水了” | 重量低于阈值(空杯)时提醒 |
| 想回顾今天的喝水情况 | 数据记录 | 本地存储(可选) |
一句话目标:把水杯变成一个“会说话的私人饮水教练”。
二、硬件准备(全用库存,成本为0)
我尽量用手里现有的东西。重新买的话也很便宜:
| ESP8266 NodeMCU | 吃灰库存 | 0元 |
| HX711 + 称重传感器(5kg量程) | 之前做厨房秤剩下的 | 0元 |
| 0.96寸OLED(I2C) | 吃灰 | 0元 |
| 蜂鸣器 | 库存 | 0元 |
| 杜邦线若干 | 库存 | 0元 |
| 薄木板/硬纸板(做杯垫外壳) | 废料 | 0元 |
| Micro USB线+充电头 | 旧手机充电器 | 0元 |
如果你什么都没:ESP8266约15元,HX711+传感器套装约12元,OLED屏约10元,蜂鸣器约1元,总计约38元。如果用ESP-01S(更便宜的模块)替代NodeMCU,还能再省几块钱。
三、工作原理
压力传感器放在杯垫下方,水杯放在压力传感器上。
-
空杯时:传感器输出一个“基准值”
-
倒水后:重量增加,传感器数值上升
-
喝水后:重量减少,传感器数值下降
累计饮水量的计算逻辑:
程序每秒读取一次重量
记录每次“重量突然下降”的差值(差值就是喝掉的量)
累加所有差值 = 今日总饮水量
如果某次差值超过500g,判定为“倒水/换杯”,不计入饮水量
提醒逻辑:
-
距离上次喝水超过40分钟 → 蜂鸣器短响,OLED显示“该喝水了!”
-
杯子重量低于“空杯基准值+50g”持续5分钟 → 显示“杯子空了,去接水!”
四、制作过程
4.1 传感器安装
压力传感器很薄,像一片不锈钢片,有四根线(红黑绿白)。接在HX711模块上,HX711再通过I2C接ESP8266。
接线:
| 红 | E+ |
| 黑 | E- |
| 绿 | A+ |
| 白 | A- |
| VCC | 3.3V |
| GND | GND |
| DT | D1 (GPIO5) |
| SCK | D2 (GPIO4) |
| VCC | 3.3V |
| GND | GND |
| SDA | D3 (GPIO0) |
| SCL | D4 (GPIO2) |
| 正极 | D5 (GPIO14) |
| 负极 | GND |
4.2 外壳制作(最丑但最实用)
我用一块薄木板(装修剩下的地板边角料)做底座,上面垫了一块鼠标垫防滑。称重传感器夹在木板和鼠标垫中间。
如果你没有木板:硬纸板叠三层也行,只要能承受水杯重量不变形。
4.3 固件开发
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <HX711.h>
// 引脚定义
#define DOUT_PIN 5 // D1
#define SCK_PIN 4 // D2
#define BUZZER_PIN 14 // D5
HX711 scale;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// 参数配置
float EMPTY_WEIGHT = 0.0; // 空杯基准重量(需校准)
float TOTAL_DRUNK = 0.0; // 今日累计饮水量(ml)
float LAST_WEIGHT = 0.0; // 上一次称重值
unsigned long LAST_DRINK_TIME = 0;
unsigned long LAST_EMPTY_TIME = 0;
void setup() {
Serial.begin(115200);
// 初始化HX711
scale.begin(DOUT_PIN, SCK_PIN);
scale.set_scale(420.0); // 校准系数,需实际标定
scale.tare(); // 去皮
// 初始化OLED
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 蜂鸣器
pinMode(BUZZER_PIN, OUTPUT);
// 校准空杯重量
delay(2000);
EMPTY_WEIGHT = scale.get_units(10);
LAST_WEIGHT = EMPTY_WEIGHT;
LAST_DRINK_TIME = millis();
displayMessage("Smart Cup", "Ready");
}
void loop() {
float currentWeight = scale.get_units(5);
// 1. 检测是否喝了水(重量突然下降)
if (currentWeight > 50 && LAST_WEIGHT – currentWeight > 30) {
float drinkAmount = LAST_WEIGHT – currentWeight;
// 过滤掉“倒水”(一次性变化大于500ml不算喝水)
if (drinkAmount < 500) {
TOTAL_DRUNK += drinkAmount;
LAST_DRINK_TIME = millis();
Serial.printf("Drank: %.1f ml, Total: %.1f ml\\n", drinkAmount, TOTAL_DRUNK);
beep(200, 1); // 喝一口响一声
}
}
// 2. 检测杯子是否空了(低于基准值+50g持续超过5分钟)
if (currentWeight < EMPTY_WEIGHT + 50) {
if (millis() – LAST_EMPTY_TIME > 300000) { // 5分钟
displayMessage("杯子空了", "去接水吧!");
beep(500, 3); // 急促响三声
LAST_EMPTY_TIME = millis();
}
} else {
LAST_EMPTY_TIME = millis(); // 重置计时器
}
// 3. 喝水提醒(超过40分钟没喝)
if (millis() – LAST_DRINK_TIME > 2400000) { // 40分钟
displayMessage("该喝水了!", "已喝 " + String(TOTAL_DRUNK, 0) + "ml");
beep(300, 2);
LAST_DRINK_TIME = millis(); // 重置,避免一直响
}
// 4. 更新OLED显示
updateDisplay(currentWeight);
LAST_WEIGHT = currentWeight;
delay(500);
}
void updateDisplay(float weight) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("今日饮水");
display.setCursor(0, 20);
display.setTextSize(2);
display.print(TOTAL_DRUNK, 0);
display.println(" ml");
display.setTextSize(1);
display.setCursor(0, 50);
display.print("杯重: ");
display.print(weight – EMPTY_WEIGHT, 0);
display.println("g");
display.display();
}
void beep(int duration, int times) {
for (int i = 0; i < times; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(duration);
digitalWrite(BUZZER_PIN, LOW);
delay(100);
}
}
void displayMessage(String line1, String line2) {
display.clearDisplay();
display.setCursor(0, 10);
display.setTextSize(2);
display.println(line1);
display.setCursor(0, 40);
display.setTextSize(1);
display.println(line2);
display.display();
}
4.4 校准
第一次使用时,先放上空杯,系统自动校准基准重量。然后用厨房秤称出水的重量,对比系统显示的“喝水量”是否一致。如果偏差大,调整代码中的scale.set_scale(420.0)这个系数(不同传感器的系数不同,用已知重量去标定)。
五、真实使用效果
5.1 一周数据
| 第1天 | ~800ml | 720ml |
| 第2天 | ~800ml | 1050ml |
| 第3天 | ~800ml | 1200ml |
| 第4天 | ~800ml | 1500ml |
| 第5天 | ~800ml | 1350ml |
| 第6天 | ~800ml | 1650ml |
| 第7天 | ~800ml | 1400ml |
一周后平均每天1500ml,比以前多了近一倍。
5.2 最有用的功能
说实话,最管用的不是蜂鸣器(我经常把它当成手机通知忽略了),而是OLED屏幕上那个不断增加的毫升数。
看到“1050ml”的时候,会想“怎么才喝这么点,再喝一杯”;看到“1650ml”的时候,会想“今天表现不错,再接再厉”。
数字让人上瘾。 这是这个项目成功的核心——它把“喝水”这个模糊的目标,变成了一个可量化的游戏。
5.3 同事的反应
放在工位上第一天,三个同事来问:“这什么玩意儿?”我解释完后,一个同事说:“帮我做一个,我愿意付材料费。”
三天后,他又来了:“做了吗?我每天喝水量比我体重的含水量还少。”
后来我帮他也做了一个,收了30块材料费。
六、可以怎么升级
| 微信推送提醒 | 接入巴法云/Server酱 | 免费 |
| 远程查看 | 接入Blynk/HomeAssistant | 免费 |
| 数据持久化 | 加一个ESP8266本地Web服务器 | 免费 |
| 更准的称重 | 换更高精度的HX711 | 约5元 |
| 不用线(无线) | 换ESP-01S + 锂电池+充电模块 | 约20元 |
| 多杯位监测 | 扩展多个HX711(需分时复用) | 每个约10元 |
如果要把数据存下来分析趋势,可以在ESP8266上跑一个简单Web服务器,或者用ESP-NOW把数据发到家里的树莓派/NAS。
七、问题与解决
❌ 问题1:传感器读数跳变
现象:什么也没动,重量数值上蹿下跳,波动±30g。
原因:压力传感器本身有噪声,加上桌面的轻微振动。
解决:代码里已经做了5次平均(scale.get_units(5)),可以增加到10次。另外把杯垫放在桌角等震动小的地方,远离机械硬盘电脑。
❌ 问题2:每次放杯位置不同,重量不一样
现象:杯子放偏一点,读数差几十克。
原因:传感器只在中心区域最灵敏,放偏了力臂不同。
解决:在杯垫表面画一个“杯子定位圈”,或者用双面胶粘一个杯垫环,强迫每次放在同一位置。
❌ 问题3:提醒被忽略
现象:蜂鸣器响了几次后,我习惯了,继续不喝。
解决:把蜂鸣器提醒改成微信推送(用巴法云,免费),或者设定“连续提醒三次后,LED闪烁不停”,直到你喝一口才重置。
❌ 问题4:换杯子/倒水被误判为喝水
现象:换了一个杯子,或者倒掉剩水,系统显示“喝了500ml”。
解决:已经在代码里设置了“单次变化超过500g判定为倒水/换杯”。如果你的杯子比较重,可以调到800g。
八、写在最后
一个30块钱的小装置,解决了我好几年都没改掉的“喝水少”问题。
说到底,物联网的价值不一定非得是“智能家居”“工业4.0”这种宏大叙事。它也可以是——提醒你该喝水了。
健康这件事,最大的敌人不是懒惰,是健忘。而技术,刚好可以帮我们对抗健忘。
现在,放下手机,喝一口水。


