基于树莓派与HX711的智能饮水提醒系统:从传感器到完整IoT项目实践
1. 项目概述与核心思路
作为一个常年伏案工作的人,我发现自己经常一坐就是大半天,手边的水杯总是满的,等想起来喝的时候,水都凉了。这不仅是习惯问题,长期饮水不足对身体的负面影响是实实在在的。市面上的智能水杯要么价格不菲,要么功能花哨不实用。于是,我萌生了一个想法:为什么不自己动手,做一个既实用又能学习技术的智能水杯支架呢?
这个项目的核心目标很简单:制作一个放在桌面上、能主动提醒我喝水的智能支架。它不仅要能感知水杯的重量(从而推算剩余水量),还要能监测杯壁附近的温湿度(了解饮水环境),并通过直观的灯光和屏幕显示来提醒我。整个系统以树莓派4B作为“大脑”,集成了DHT11温湿度传感器、HX711称重模块、LCD显示屏和RGB灯带,是一个典型的物联网(IoT)入门级硬件集成项目。
选择树莓派而非更简单的单片机(如Arduino),主要是看中其强大的通用性和扩展能力。树莓派运行完整的Linux系统,可以轻松地用Python进行快速原型开发,未来如果想增加数据上传云端、微信推送、语音提醒等高级功能,也几乎没有门槛。对于硬件爱好者或嵌入式初学者来说,这是一个绝佳的练手项目,能让你一次性接触到模拟/数字传感器、SPI通信、GPIO控制、简单的3D结构设计等多个关键知识点。
2. 硬件选型与电路设计解析
硬件是项目的骨架,选型直接决定了系统的稳定性、精度和成本。我的核心原则是:在满足功能需求的前提下,优先选择成熟、易用、文档丰富的模块。
2.1 核心控制器:树莓派4B
树莓派4B是项目的计算与控制中心。我选择它的理由很充分:
- 充足的GPIO引脚:本项目需要连接多个传感器和外设,树莓派40针的GPIO接口提供了足够的数字IO、PWM和硬件SPI接口。
- 强大的处理能力:运行一个包含传感器数据读取、逻辑判断、屏幕刷新和网络通信(预留)的Python脚本,对四核Cortex-A72的树莓派4B来说游刃有余。
- 完善的生态与社区:任何遇到的问题,几乎都能在社区找到答案。丰富的Python库(如
RPi.GPIO,spidev,Adafruit_DHT)让开发变得异常简单。 - 灵活的供电:标准的USB-C 5V/3A供电,非常稳定,也能为外设(如LCD屏、灯带)提供足够的电力。
注意:树莓派3B+或更新的Zero 2 W也可以胜任此项目。如果追求极致低成本和小体积,Zero 2 W是很好的选择,但需要自行焊接排针或使用转接板,对新手稍有挑战。
2.2 感知层:传感器模块详解
1. 重量感知:HX711与应变式称重传感器这是监测水量的核心。我选用了一个5kg量程的悬臂梁式称重传感器(俗称“秤重传感器”)和HX711模数转换芯片。
- 原理:称重传感器内部是惠斯通电桥,当受力发生形变时,桥臂电阻变化,输出一个微弱的差分电压信号(通常是毫伏级)。
- 为什么需要HX711?树莓派的GPIO只能读取数字信号(高/低电平),而这个微弱的模拟电压信号无法直接识别。HX711芯片的作用就是将这个模拟信号放大并转换为树莓派可以读取的24位数字信号。24位的高分辨率意味着即使重量变化很小,也能被精确捕捉到。
- 选型考量:5kg量程对于一个装满水的水杯(通常1-2kg)来说绰绰有余,留有余量可以保证传感器工作在线性度最好的区间,精度更高。HX711几乎是树莓派称重项目的标配,驱动成熟。
2. 环境感知:DHT11温湿度传感器DHT11是一个复合传感器,能同时测量温度和湿度。它输出的是经过校准的数字信号,通过单总线协议与树莓派通信。
- 优点:价格极其低廉,接线简单(仅需一根数据线),有现成的Python库支持。
- 精度注意:DHT11的湿度精度为±5%RH,温度精度为±2°C。对于桌面环境监测和粗略的“水温推断”提醒(例如,环境温度低时,水可能凉得快)来说完全足够。如果对精度要求高,可以考虑DHT22或SHT31,但价格和接线复杂度会上升。
3. 备用方案:LM35温度传感器与MCP3008 ADC在原始设计中,我还使用了LM35模拟温度传感器和MCP3008模数转换芯片。LM35输出的是与温度成正比的模拟电压(10mV/°C)。由于树莓派没有内置ADC(模数转换器),所以需要外接MCP3008这样的ADC芯片来读取其电压值。
- 实际应用思考:在这个项目中,DHT11已经提供了温度数据。LM35可以作为杯壁温度的一个补充监测点(例如,贴在杯壁上),但需要额外的结构设计。对于初版,可以专注于一个主要温度源以简化系统。MCP3008是一个8通道10位ADC,如果你未来想扩展更多模拟传感器(如光敏电阻、土壤湿度传感器),它会非常有用。
2.3 交互层:显示与灯光反馈
1. LCD1602液晶显示屏我选择了最经典的16x2字符型LCD蓝屏白字显示屏。它通过并行接口与树莓派连接,虽然需要较多连线(11根),但显示信息稳定、直观。
- 驱动方式:通常使用
RPLCD库或直接操作GPIO来驱动。它非常适合显示固定的提示语句、实时重量、温度湿度数据等。 - 对比度调节:必须连接一个10kΩ的可调电位器(电位器)到其VO引脚(第3脚),用于调节屏幕对比度,否则可能什么都看不到。
2. RGB LED灯带我选用的是5V供电、共阳极的RGB LED灯带。灯带本身是一个整体,但我们可以通过PWM(脉冲宽度调制)来控制其红、绿、蓝三个通道的亮度,从而混合出任何颜色。
- 为什么需要MOSFET?树莓派GPIO引脚只能提供有限的电流(通常<16mA),而驱动LED灯带需要更大的电流。MOSFET(金属氧化物半导体场效应晶体管)在这里作为电子开关使用。树莓派的GPIO输出一个微弱的控制信号给MOSFET的栅极(G),来控制其源极(S)和漏极(D)之间的大电流通断,从而安全地驱动灯带。我用了三个MOSFET分别控制红、绿、蓝三色。
- PWM调光:通过
RPi.GPIO库的PWM功能,可以改变GPIO输出方波的占空比,从而实现从0到100%的亮度调节,实现丰富的颜色和呼吸灯等效果。
2.4 电路连接总图与供电考量
将所有模块连接到树莓派时,务必注意共地(将所有模块的GND引脚连接到树莓派的GND引脚),这是电路正常工作的基础。树莓派提供了3.3V和5V两种电压输出:
- 3.3V:适用于DHT11、MCP3008等逻辑电平为3.3V的芯片。
- 5V:适用于HX711、LCD1602、RGB灯带等需要5V供电或逻辑电平的模块。
重要经验:务必确认传感器和芯片的电压兼容性。将5V信号直接接入树莓派3.3V的GPIO引脚可能会损坏树莓派。本项目中,HX711虽然由5V供电,但其数据引脚输出的是3.3V电平,因此可以直接连接树莓派GPIO,这是安全的。
3. 软件架构与核心代码实现
软件是项目的灵魂,负责调度所有硬件,并实现智能逻辑。我采用Python进行开发,因为它语法简洁、库丰富,非常适合树莓派上的快速原型开发。
3.1 开发环境与依赖库准备
首先,确保树莓派系统已更新,并安装必要的库:
# 更新系统 sudo apt update && sudo apt upgrade -y # 安装Python3开发工具(通常已预装) sudo apt install python3-dev python3-pip -y # 安装核心GPIO控制库 sudo apt install python3-rpi.gpio -y # 使用pip安装其他传感器专用库 sudo pip3 install Adafruit_DHT # DHT11驱动 sudo pip3 install hx711 # HX711驱动,可能需要根据具体版本调整 sudo pip3 install RPLCD # LCD1602驱动 # 对于MCP3008,可以使用spidev库(通常系统已带)或安装Adafruit_MCP3008 sudo pip3 install Adafruit_MCP30083.2 模块化编程:构建传感器驱动类
良好的代码结构是项目可维护的关键。我为每个主要硬件模块编写了独立的类或函数。
1. 重量读取模块 (WeightSensor)
import time import threading from hx711 import HX711 class WeightSensor: def __init__(self, dout_pin=5, pd_sck_pin=6): """ 初始化HX711 :param dout_pin: 数据引脚 (GPIO5) :param pd_sck_pin: 时钟引脚 (GPIO6) """ self.hx = HX711(dout_pin, pd_sck_pin) # 1. 设置参考单位(需校准) self.reference_unit = -411.5 # 这个值需要根据你的传感器实测校准 self.hx.set_reference_unit(self.reference_unit) # 2. 复位并去皮(置零) self.hx.reset() self.tare() # 3. 当前重量 self.current_weight_grams = 0 # 4. 启用后台线程持续读取 self.running = True self.thread = threading.Thread(target=self._update_loop) self.thread.start() def tare(self): """去皮功能,将当前重量设为零点""" self.hx.tare() def _update_loop(self): """内部线程函数,持续读取重量""" while self.running: try: # 读取多次取平均值,提高稳定性 vals = self.hx.get_raw_data(times=3) if vals: weight = sum(vals) / len(vals) self.current_weight_grams = weight time.sleep(0.5) # 每秒更新2次 except Exception as e: print(f"读取重量出错: {e}") time.sleep(1) def get_weight(self): """获取当前重量(克)""" return self.current_weight_grams def cleanup(self): """清理资源,停止线程""" self.running = False if self.thread.is_alive(): self.thread.join() self.hx.power_down()校准心得:
reference_unit是核心参数。你需要用一个已知重量的物体(如500g砝码或一瓶未开封的500ml矿泉水)进行校准。公式为:reference_unit = 已知重量 / (读取的原始值 - 空载原始值)。空载时先执行tare()去皮。这个过程可能需要反复几次才能得到稳定准确的值。
2. 温湿度读取模块 (DHT11Reader)
import Adafruit_DHT import time class DHT11Reader: SENSOR_TYPE = Adafruit_DHT.DHT11 def __init__(self, gpio_pin=23): self.gpio_pin = gpio_pin def read(self): """ 读取一次温湿度数据 :return: (湿度, 温度) 或 (None, None) 如果读取失败 """ humidity, temperature = Adafruit_DHT.read_retry(self.SENSOR_TYPE, self.gpio_pin, retries=2, delay_seconds=0.1) return humidity, temperature避坑提示:DHT11读取偶尔会失败,返回
None。read_retry函数提供了重试机制,但重试间隔不宜过短,否则会加剧失败。在实际应用中,最好加入异常处理和历史数据缓存,当一次读取失败时,使用上一次的有效值。
3. LCD显示控制模块 (LCDDisplay)
from RPLCD.gpio import CharLCD import RPi.GPIO as GPIO class LCDDisplay: def __init__(self): # 根据实际接线定义引脚 self.lcd = CharLCD(pin_rs=4, pin_rw=None, pin_e=17, pins_data=[27, 22, 13, 19], numbering_mode=GPIO.BCM, cols=16, rows=2, dotsize=8) self.lcd.clear() def show_message(self, line1, line2, clear=True): """在LCD上显示两行信息""" if clear: self.lcd.clear() self.lcd.write_string(line1.ljust(16)[:16]) # 左对齐并截断 self.lcd.cursor_pos = (1, 0) self.lcd.write_string(line2.ljust(16)[:16]) def cleanup(self): self.lcd.clear() self.lcd.close(clear=True)4. RGB灯带控制模块 (RGBLight)
import RPi.GPIO as GPIO import time class RGBLight: def __init__(self, pins={'red': 18, 'green': 24, 'blue': 16}): GPIO.setmode(GPIO.BCM) self.pins = pins self.pwm_objects = {} # 初始化所有引脚为PWM输出 for color, pin in self.pins.items(): GPIO.setup(pin, GPIO.OUT) pwm = GPIO.PWM(pin, 100) # 频率100Hz pwm.start(0) # 初始占空比为0(灯灭) self.pwm_objects[color] = pwm def set_color(self, r, g, b): """设置RGB颜色,参数范围0-100(占空比%)""" self.pwm_objects['red'].ChangeDutyCycle(r) self.pwm_objects['green'].ChangeDutyCycle(g) self.pwm_objects['blue'].ChangeDutyCycle(b) def breath_effect(self, color='blue', duration=2): """呼吸灯效果""" for i in range(0, 101, 5): # 渐亮 self.pwm_objects[color].ChangeDutyCycle(i) time.sleep(duration/40) for i in range(100, -1, -5): # 渐暗 self.pwm_objects[color].ChangeDutyCycle(i) time.sleep(duration/40) def cleanup(self): for pwm in self.pwm_objects.values(): pwm.stop() GPIO.cleanup(list(self.pins.values()))3.3 主程序逻辑:状态机与智能提醒
主程序的核心是一个简单的状态机,它根据当前水量和用户设定的目标,决定系统的行为(显示什么、灯光如何变化)。
import time from weight_sensor import WeightSensor from dht11_reader import DHT11Reader from lcd_display import LCDDisplay from rgb_light import RGBLight class SmartWaterStand: def __init__(self): print("初始化智能水杯支架...") self.weight_sensor = WeightSensor() self.dht11 = DHT11Reader() self.lcd = LCDDisplay() self.light = RGBLight() # 用户参数配置 self.cup_empty_weight = 50 # 空杯重量 (克),需校准 self.cup_full_weight = 550 # 满杯重量 (克),需校准 self.target_daily_ml = 2000 # 每日目标饮水量 (毫升) self.current_ml = 0 # 今日已饮水总量 self.last_drink_time = time.time() self.reminder_interval = 30 * 60 # 提醒间隔(秒),30分钟 # 系统状态 self.state = "NORMAL" def calculate_water_ml(self, current_weight_g): """根据当前重量计算剩余水量(毫升),1克约等于1毫升水""" if current_weight_g <= self.cup_empty_weight: return 0 elif current_weight_g >= self.cup_full_weight: return self.cup_full_weight - self.cup_empty_weight else: return current_weight_g - self.cup_empty_weight def update_display(self, water_ml, temp, humidity): """更新LCD屏幕显示""" line1 = f"Water:{water_ml:4d}ml {temp:4.1f}C" line2 = f"Goal:{self.target_daily_ml:4d}ml Hum:{humidity:3.0f}%" self.lcd.show_message(line1, line2, clear=False) def evaluate_and_remind(self, water_ml): """评估状态并触发提醒""" now = time.time() time_since_last_drink = now - self.last_drink_time if water_ml < 100: # 水量低于100ml self.state = "LOW_WATER" self.light.set_color(100, 0, 0) # 红色常亮,强烈提醒加水 if time_since_last_drink > self.reminder_interval: # 长时间未喝水且水快没了,双重重置 self.light.breath_effect('red', 1) # 红色呼吸灯加强提醒 elif time_since_last_drink > self.reminder_interval: self.state = "TIME_TO_DRINK" self.light.set_color(0, 0, 100) # 蓝色常亮,提醒该喝水了 else: self.state = "NORMAL" # 根据水量渐变灯光:水多绿色,水少黄色 water_ratio = water_ml / (self.cup_full_weight - self.cup_empty_weight) green = int(100 * water_ratio) red = int(100 * (1 - water_ratio)) self.light.set_color(red, green, 0) # 如果检测到喝水动作(重量突然减少一定值) if self._detect_drinking_action(water_ml): drunk_ml = self._estimate_drink_volume() self.current_ml += drunk_ml self.last_drink_time = now print(f"检测到饮水动作,约喝了{drunk_ml}ml,今日累计{self.current_ml}ml") self.light.set_color(0, 100, 0) # 绿色闪烁反馈 time.sleep(0.5) self.light.set_color(0, 0, 0) time.sleep(0.2) def _detect_drinking_action(self, current_water_ml): """简易喝水动作检测(通过重量骤降判断)""" # 这里需要维护一个短暂的历史重量队列,判断短时间内是否下降超过阈值 # 为简化示例,此处省略具体实现,可用一个列表缓存最近几秒的重量值进行判断 pass def _estimate_drink_volume(self): """估算单次饮水量(基于重量变化)""" # 需要结合历史重量数据计算 # 简化处理:返回一个固定估算值或基于最近重量差计算 return 150 # 假设每次喝150ml def run(self): """主循环""" print("系统启动,开始监控...") try: while True: # 1. 读取数据 weight_g = self.weight_sensor.get_weight() humidity, temperature = self.dht11.read() water_ml = self.calculate_water_ml(weight_g) # 2. 更新显示 self.update_display(water_ml, temperature, humidity) # 3. 状态判断与提醒 self.evaluate_and_remind(water_ml) # 4. 控制循环频率 time.sleep(2) # 每2秒更新一次 except KeyboardInterrupt: print("\n用户中断,正在清理资源...") finally: self.cleanup() def cleanup(self): """安全关闭所有硬件""" self.weight_sensor.cleanup() self.lcd.cleanup() self.light.cleanup() print("资源清理完毕。") if __name__ == "__main__": stand = SmartWaterStand() stand.run()4. 机械结构与外壳设计
硬件需要有一个“家”,一个稳固、美观且实用的外壳。我选择了3D打印来制作这个外壳,因为它非常适合制作这种复杂程度不高、但需要定制化结构的原型。
4.1 设计思路与要点
- 分层结构:我将外壳设计为两层。底层是“设备仓”,用于固定树莓派、面包板(或PCB)、电源模块和各种接线端子,确保线路整洁且安全。上层是“承载台”,中心有一个凹陷的圆形区域,用于放置水杯,其正下方与称重传感器受力点刚性连接。
- 传感器定位:
- 称重传感器:必须被稳固地安装在承载台和设备仓之间,确保水杯的全部重量能有效地通过设计好的力臂传递到传感器的受力点上。我设计了一个专门的“传感器卡座”,既能固定传感器本体,又能让它的应变梁悬空受力。
- DHT11/LM35:需要开孔,让传感器的探测头能微微露出到承载台表面,贴近杯壁,但又不能影响水杯放置。可以在杯托侧面开一个小孔。
- LCD屏幕:在前面板开一个矩形窗口,用热熔胶或卡扣从内部固定。
- RGB灯带:可以嵌入在承载台边缘或设备仓的侧面,作为氛围灯。我选择将其贴在设备仓内侧,光线通过半透明的亚克力面板透出,效果更柔和。
- 散热与维护:在设备仓侧面设计通风栅格,帮助树莓派散热。底盖设计为可拆卸式(通过螺丝固定),方便日后检修或升级硬件。
- 走线管理:在设计时就要预留线槽或过线孔,让杜邦线可以有序地从设备仓连接到上层的各个传感器和屏幕,避免杂乱。
4.2 3D打印实践与后处理
- 建模软件:我使用Fusion 360进行设计,它对个人用户免费,且非常适合参数化机械设计。
- 打印设置:
- 材料:PLA即可,成本低,强度足够。如果环境较热,可以考虑PETG。
- 层高:0.2mm,在打印质量和时间间取得平衡。
- 填充率:15%-20%。对于非受力结构件,这个填充率足够,还能节省材料和时间。
- 支撑:对于有悬空结构的部分(如传感器卡座的悬臂),需要生成支撑。记得在切片软件中仔细检查。
- 后处理:打印完成后,去除支撑,用砂纸打磨结合面,确保上下盖能平整闭合。可以在内部涂刷一层绝缘清漆,防止可能的短路。
给非3D打印用户的建议:完全可以用亚克力板、木板甚至乐高积木来搭建这个支架。核心是制作一个稳固的平台,并将称重传感器正确地安装在其承重路径上。一个简单的方案:用两块大小不同的木板,中间通过四个角上的弹簧或橡胶柱支撑,将称重传感器安装在其中一个角上。虽然精度和美观度可能不如3D打印,但完全可行。
5. 系统集成、校准与调试实录
这是将代码、电路和结构体组装成一个可靠产品的关键步骤,也是最容易出问题的阶段。
5.1 分步集成与上电测试
绝对不要一次性接好所有线再通电!务必采用分步集成法:
- 最小系统测试:只连接树莓派电源,通过SSH或屏幕确认其能正常启动。
- 逐个添加传感器:
- 先接HX711与称重传感器。编写一个简单的测试脚本,读取原始AD值,观察用手按压时数值是否线性变化。确认无误后再进行校准。
- 再接DHT11,运行测试脚本看能否读到合理的温湿度值(对着传感器哈气,湿度应上升)。
- 然后接LCD,先不接电位器,上电后可能看到一排黑方块,这是正常的。接上电位器并调节,直到字符清晰显示。
- 最后接RGB灯带与MOSFET,先用测试脚本分别控制红、绿、蓝三色,确认都能正常点亮且亮度可调。
- 集成测试:所有硬件单独测试通过后,再将它们全部接入,运行主程序。此时应能看到LCD显示数据,灯带根据重量变化变色。
5.2 核心校准流程详解
1. 称重系统校准(最关键)校准是保证水量测量准确的唯一途径。你需要两个已知重量的物体:一个是“空载”(即空杯架本身),一个是“标定重量”。
- 步骤一:去皮(Tare)
- 将空载的杯架(不放置水杯)平稳放在桌面上。
- 运行校准脚本,调用HX711的
tare()函数。这个操作会将当前的传感器读数设为零点,抵消杯架自身重量。
- 步骤二:标定(Calibrate)
- 将一个已知精确重量的物体(例如,一瓶500g的砝码,或一瓶未开封的500ml矿泉水,其重量会标在标签上)平稳放在杯架中心。
- 读取此时HX711的原始读数(
get_raw_data的平均值),假设为raw_value_full。 - 计算参考单位(
reference_unit):reference_unit = known_weight / (raw_value_full - raw_value_empty)其中raw_value_empty是去皮后的原始值(理论上应为0或接近0)。 - 将这个计算出的
reference_unit值填入代码中WeightSensor类的初始化参数里。
- 验证:移走标定物,放上空水杯,记录读数。再向杯中倒入100ml水(水的密度1g/ml,即100g),查看读数增加是否接近100g。反复测试几次。
2. 杯重与满杯重量设定在SmartWaterStand类的__init__中,需要设置cup_empty_weight和cup_full_weight。
cup_empty_weight:放上你常用水杯(空的)时的稳定重量(克)。cup_full_weight:放上装满水的水杯时的稳定重量(克)。 这两个值可以通过校准后的系统直接读取并填入代码。
5.3 常见问题与排查技巧
以下是我在调试过程中踩过的坑和解决方案:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 树莓派无法启动或频繁重启 | 供电不足。RGB灯带、LCD屏同时工作时峰值电流可能超过树莓派USB口或劣质电源的供电能力。 | 1. 使用官方或认证的5V/3A以上电源适配器。 2. 尝试先断开RGB灯带和LCD屏,看树莓派能否稳定启动。 3. 考虑为灯带单独供电(外部5V电源),并将其地与树莓派地连接。 |
| HX711读数不稳定或为0 | 1. 接线错误或接触不良。 2. 供电电压不稳。 3. 传感器或HX711模块损坏。 4. 参考单位未校准或校准错误。 | 1. 用万用表检查VCC和GND是否接通,电压是否稳定(5V)。 2. 检查四根线是否按颜色正确连接到HX711(红-E+,黑-E-,白-A-,绿-A+)。 3. 运行仅读取原始值的测试脚本,用手轻压传感器,看原始AD值是否大幅变化。若无变化,可能是传感器问题。 4. 重新执行校准流程,确保标定物重量准确、放置平稳。 |
DHT11偶尔返回None | 单总线通信对时序敏感,可能受其他GPIO操作干扰或物理连接问题。 | 1. 确保数据线连接正确且接触良好,上拉电阻(如果模块上没有)是否已接(4.7kΩ-10kΩ上拉到3.3V)。 2. 在 read_retry函数中增加重试次数和延迟。3. 在代码中捕获异常,并返回上一次的有效读数,避免程序因单次读取失败而中断。 |
| LCD屏幕显示乱码或全黑/全白 | 1. 对比度电位器未调节。 2. 接线错误,特别是RS、E、数据线D4-D7。 3. 初始化序列不正确。 | 1.首先调节电位器!这是最常见的原因,慢慢旋转直到字符清晰出现。 2. 对照引脚定义表,逐根检查接线。 3. 确认代码中使用的引脚编号模式( GPIO.BCM或GPIO.BOARD)与实际物理引脚对应关系一致。 |
| RGB灯带颜色不对或亮度异常 | 1. MOSFET引脚接错(G、D、S)。 2. 共阳极/共阴极接错。我使用的是共阳极灯带(+5V公共),控制端低电平有效(通过MOSFET接地)。 3. PWM频率设置不当。 | 1. 确认MOSFET型号,确保栅极(G)接GPIO,漏极(D)接灯带颜色线,源极(S)接地。 2. 单独测试每个颜色:将对应GPIO设置为100%占空比,灯应最亮;0%占空比则灯灭。 3. PWM频率一般设在100Hz-1000Hz即可,过低会闪烁,过高可能某些MOSFET响应不及。 |
| 喝水动作检测误触发 | 算法过于敏感,将桌面震动或手部触碰误判为喝水。 | 1. 在_detect_drinking_action函数中增加“死区”和“持续判断”。例如,重量必须在2秒内持续下降超过50克才被判定为喝水,且下降后重量需稳定一段时间。2. 加入简单的滤波算法,如移动平均滤波,平滑重量数据。 |
5.4 功能优化与扩展思路
当基础版本稳定运行后,你可以考虑以下升级,让项目更具实用性:
- 数据可视化与云端同步:使用
Flask或FastAPI在树莓派上搭建一个简单的Web服务器,实时显示水量、温湿度曲线。更进一步,可以将数据定时上传到ThingsBoard、Home Assistant或自建的InfluxDB数据库中,实现长期统计和历史回顾。 - 多平台消息推送:集成
Bark(iOS)、Server酱(微信)或Telegram BotAPI,当长时间未喝水或水杯已空时,向手机发送推送通知。 - 语音提醒:连接一个USB小音箱或PWM扬声器,使用
pyttsx3或gTTS库,在提醒时播放“该喝水啦!”等语音提示。 - 自适应学习:记录用户每天的饮水习惯和时间,利用简单的算法(如计算平均值)动态调整提醒间隔,使其更个性化。
- 低功耗优化:如果希望便携,可以考虑将主控换为树莓派Zero 2 W,并优化代码,在无人使用时进入低功耗状态,仅由称重传感器中断唤醒。
完成这个项目后,我桌上多了一个默默工作的“饮水管家”。它不再是一个冰冷的电子作业,而是一个真正融入日常、带来改变的小工具。每当灯带变成蓝色,或是屏幕上的数字提醒我今日目标尚未完成,我都会会心一笑,端起水杯。这个过程里,最大的收获不是最终的那个支架,而是从需求分析、硬件选型、电路焊接、代码调试到结构组装的全流程实践。每一个踩过的坑,都让下一个项目的路更加平坦。如果你也对硬件和物联网感兴趣,不妨就从这样一个贴近生活的小项目开始,亲手创造一个属于自己的智能小物件。
