尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

树莓派DIY智能灌溉控制器:MQTT+继电器HAT+土壤湿度传感器实战

树莓派DIY智能灌溉控制器:MQTT+继电器HAT+土壤湿度传感器实战 1. 项目缘起与整体设计思路1.1 为什么我要用树莓派做一套自动灌溉控制器家里有个小院子种了些番茄、辣椒和几盆月季。前两年夏天出差一周回来一看番茄叶子全蔫了辣椒直接干死两棵。请邻居帮忙浇水吧人家也有自己的事不好意思天天麻烦。市面上卖的成品灌溉定时器我也看过便宜的只能按固定时间开关不管下没下雨、土干不干到点就浇贵一点的带WiFi的功能是多了但要么绑定厂商云服务要么App做得稀烂数据还攥在别人手里。后来一想我手头正好有块树莓派4B在吃灰加上一个继电器扩展板干脆自己搭一套。Sprinqua这个项目就是在这个背景下折腾出来的。名字是我自己起的Sprinkler加Aqua的组合核心思路很直接用树莓派作为大脑通过继电器控制电磁阀结合土壤湿度传感器和天气预报数据决定什么时候开阀、开多久。所有逻辑跑在本地数据通过MQTT协议在局域网内流转手机、电脑、甚至Home Assistant都能订阅状态。整套东西做下来硬件成本不到三百块功能却比市面上七八百的成品更贴合自己的需求。这套方案适合谁呢如果你家里有院子、阳台菜箱、或者小型温室又刚好有一块树莓派在抽屉里躺着那这篇文章就是写给你的。不需要你懂太多电子知识会插杜邦线、能照着步骤敲命令就行。我会把踩过的坑、选型时的纠结、以及最后跑通的那套配置全部摊开讲。1.2 核心架构拆解从传感器到电磁阀的完整链路整套系统的逻辑链路其实不复杂我画个文字版的架构图帮你理解感知层土壤湿度传感器插在花坛里实时读取土壤含水量。我用的是一款电容式传感器相比电阻式的不容易腐蚀寿命长很多。同时树莓派通过HTTP请求拉取本地天气预报数据判断未来几小时会不会下雨。决策层树莓派上跑一个Python主程序每隔一段时间读取传感器数据结合天气预报和预设的阈值规则决定是否触发灌溉。规则可以很灵活比如“土壤湿度低于30%且未来两小时无降雨”才开阀。执行层继电器HAT插在树莓派的40针GPIO上通过GPIO引脚的高低电平控制继电器通断。继电器另一端接电磁阀的电源回路电磁阀装在水管上通电打开、断电关闭。通信层所有状态数据通过MQTT协议发布到本地MQTT Broker。我选的是Mosquitto轻量、稳定、资源占用低。手机上的MQTT客户端可以订阅主题随时查看土壤湿度、阀门状态、上次灌溉时间。Home Assistant也能通过MQTT自动发现这些实体接入仪表盘。供电树莓派用5V/3A的USB-C电源电磁阀一般是12V或24V直流需要单独的电源适配器。继电器模块负责隔离这两个电压域保护树莓派不被反向电动势打坏。这个架构的好处是每一层都可以独立替换。传感器坏了换传感器电磁阀升级换电磁阀决策逻辑想改就改Python代码通信协议不喜欢MQTT也可以换成HTTP或者WebSocket。没有厂商锁定所有东西都在自己手里。1.3 硬件选型为什么是继电器HAT而不是普通继电器模块一开始我用的是那种几块钱的蓝色继电器模块四路的那种直接杜邦线插在GPIO上。能用但有两个问题一是接线乱杜邦线容易松有次浇水浇到一半继电器接触不良水阀一直开着把花坛淹了二是没有电平转换树莓派GPIO是3.3V逻辑有些继电器模块是5V触发的虽然大多数情况下3.3V也能触发但可靠性存疑。后来换了继电器HAT就是那种直接扣在树莓派40针排针上的扩展板。好处很明显物理连接牢固不会因为震动松脱板子上自带电平转换和光耦隔离3.3V信号能稳定驱动继电器走线整洁整个树莓派加HAT可以塞进一个标准外壳里。我用的是一款四路继电器HAT每路支持250V/10A的交流或30V/10A的直流负载控制电磁阀绰绰有余。电磁阀我选的是12V直流常闭型口径根据水管粗细选4分管。常闭的意思是断电时阀门关闭通电才打开。这个选择很关键——万一树莓派死机或者断电阀门会自动关闭不会一直浇水。如果你选常开型断电时阀门打开那就等着水漫金山吧。土壤湿度传感器我前后试过三种。第一种是电阻式两个探针插在土里靠电阻变化测湿度。便宜但探针电解腐蚀很快两个月就锈得不成样子。第二种是电容式表面有阻焊层不直接接触土壤寿命长很多精度也够用。第三种是I2C接口的数字传感器精度最高但价格也最贵而且需要校准。最后我选了电容式性价比最平衡。2. 核心细节解析与实操要点2.1 继电器HAT的接线与GPIO引脚分配继电器HAT扣上去之后你需要知道哪个继电器对应哪个GPIO引脚。不同厂家的HAT引脚定义可能不一样我手上这块是用的BCM编号17、27、22、23分别对应四路继电器。接线之前一定要查清楚你手上那块HAT的文档接错了轻则不动作重则烧GPIO。电磁阀的接线分两部分控制回路和电源回路。控制回路就是继电器输出端一般标着COM和NO常开或NC常闭。我们用的是常闭型电磁阀所以电磁阀的电源正极接继电器的COM电磁阀的负极接电源负极电源正极再接到继电器的NO。这样继电器吸合时COM和NO导通电磁阀通电打开继电器释放时COM和NO断开电磁阀断电关闭。注意电磁阀属于感性负载断电瞬间会产生反向电动势可能干扰树莓派甚至损坏GPIO。建议在电磁阀两端并联一个续流二极管比如1N4007阴极接电源正极阳极接电源负极。继电器HAT本身有光耦隔离但加个二极管更保险。电源方面树莓派和电磁阀一定要分开供电。树莓派用5V/3A的USB-C电源电磁阀用12V/2A的直流电源适配器。两个电源的负极可以共地但正极绝对不能接在一起。我见过有人图省事用一个12V电源加降压模块给树莓派供电结果电磁阀一开树莓派就重启就是电源功率不够加上干扰导致的。2.2 土壤湿度传感器的校准与数据读取电容式土壤湿度传感器输出的是模拟电压树莓派没有模拟输入引脚所以需要加一个ADC芯片比如MCP3008。MCP3008是8通道10位ADCSPI接口接线简单Python库也成熟。接线方式MCP3008的VDD和VREF接3.3VAGND和DGND接地CLK接GPIO11SCLKDOUT接GPIO9MISODIN接GPIO10MOSICS接GPIO8CE0。传感器的模拟输出接MCP3008的CH0通道。校准是必须做的。传感器在空气中读到的值和在水中读到的值差别很大你需要知道这两个极值才能把原始读数映射成湿度百分比。我的做法是先把传感器擦干放在空气中读一个值记为dry_value然后插进一杯水里读一个值记为wet_value。之后每次读数用这个公式换算湿度百分比 (dry_value - raw_value) / (dry_value - wet_value) * 100%。实测下来电容式传感器在空气中读数大概在520左右水中大概在260左右不同批次会有差异所以每换一个传感器都要重新校准。校准之后土壤湿度低于30%就触发灌溉这个阈值可以根据你的植物类型调整。多肉植物可能20%就够了叶菜类可能需要40%。2.3 MQTT主题设计与消息格式约定MQTT是整个系统的通信骨架主题设计得好不好直接影响到后续扩展和集成的便利性。我用的主题结构是这样的sprinqua/sensor/soil_moisture发布土壤湿度百分比payload是纯数字比如35.2sprinqua/sensor/temperature发布温度payload是纯数字sprinqua/valve/state发布阀门状态payload是on或offsprinqua/valve/control订阅这个主题收到on就开阀收到off就关阀sprinqua/system/status发布系统状态payload是JSON包含uptime、last_irrigation、next_check等消息格式我倾向于简单直接。传感器数据用纯数字方便Home Assistant直接解析成数值实体。控制指令用简单字符串方便手动测试。系统状态用JSON因为字段多JSON结构清晰。提示MQTT主题不要用中文不要用空格不要用特殊字符。用斜杠分层层级不要太深三层足够了。另外建议开启MQTT的保留消息功能这样新订阅的客户端能立刻收到最后一条状态不用等下一次发布。Mosquitto的配置也很简单默认监听1883端口允许匿名访问。如果只在局域网内用这样就行。如果想加认证可以配置用户名密码但会增加客户端配置的复杂度。我的建议是局域网内先跑通有外网访问需求再考虑加认证和TLS。2.4 天气预报数据的获取与解析天气预报我用的是一个免费的天气API具体名字就不说了很多平台都提供类似服务。核心思路是用树莓派的经纬度请求未来几小时的降雨概率如果降雨概率超过60%就跳过本次灌溉。API返回的是JSON格式Python用requests库请求然后用json库解析。关键字段是hourly数组里的precip_probability。我一般取未来三小时的降雨概率最大值如果超过阈值就不浇。这里有个坑免费API通常有调用频率限制比如每天1000次。如果你每分钟都请求一次很快就会用完。我的做法是每30分钟请求一次把结果缓存起来决策时用缓存数据。另外要处理网络异常如果API请求失败就降级为只根据土壤湿度决策不能因为网络问题就不浇水了。import requests import json from datetime import datetime, timedelta def get_rain_probability(lat, lon): try: url fhttps://api.example.com/forecast?lat{lat}lon{lon} resp requests.get(url, timeout10) data resp.json() hourly data.get(hourly, []) max_prob 0 for hour in hourly[:3]: prob hour.get(precip_probability, 0) if prob max_prob: max_prob prob return max_prob except Exception as e: print(f天气API请求失败: {e}) return 0这段代码的逻辑是请求未来三小时的降雨概率取最大值返回。如果请求失败返回0表示“没有降雨”这样系统会继续根据土壤湿度决策不会因为网络问题导致植物渴死。3. 实操过程与核心环节实现3.1 系统环境准备与依赖安装树莓派系统我用的Raspberry Pi OS Lite64位版本。Lite版没有桌面环境资源占用少适合这种后台服务。烧录系统、配置WiFi、开启SSH这些基础操作就不展开了网上教程很多。系统起来之后第一件事是更新软件源和升级已安装的包sudo apt update sudo apt upgrade -y然后安装必要的依赖sudo apt install -y python3-pip python3-dev mosquitto mosquitto-clients libgpiod2 pip3 install RPi.GPIO spidev requests paho-mqtt这里解释一下每个包的作用。mosquitto是MQTT Brokermosquitto-clients提供命令行工具用来测试发布订阅。libgpiod2是GPIO控制库的底层依赖。RPi.GPIO是Python操作GPIO的库spidev是SPI通信库用来读MCP3008。requests用来请求天气APIpaho-mqtt是Python的MQTT客户端库。安装完成后启动Mosquitto并设置开机自启sudo systemctl enable mosquitto sudo systemctl start mosquitto验证一下MQTT服务是否正常mosquitto_sub -t test -v另开一个终端mosquitto_pub -t test -m hello如果第一个终端收到了test hello说明MQTT服务正常。3.2 继电器控制代码的编写与测试先写一个最简单的继电器控制脚本验证硬件接线是否正确import RPi.GPIO as GPIO import time VALVE_PIN 17 GPIO.setmode(GPIO.BCM) GPIO.setup(VALVE_PIN, GPIO.OUT) GPIO.output(VALVE_PIN, GPIO.HIGH) # 继电器释放阀门关闭 def open_valve(): GPIO.output(VALVE_PIN, GPIO.LOW) # 继电器吸合阀门打开 print(阀门已打开) def close_valve(): GPIO.output(VALVE_PIN, GPIO.HIGH) print(阀门已关闭) if __name__ __main__: try: open_valve() time.sleep(5) close_valve() except KeyboardInterrupt: pass finally: GPIO.cleanup()注意这里的高低电平逻辑。大多数继电器HAT是低电平触发也就是GPIO输出LOW时继电器吸合。但也有一些是高电平触发具体要看你手上的HAT。如果不确定先用万用表测一下或者听继电器有没有“咔嗒”声。注意测试的时候电磁阀可以先不接只测继电器动作。确认继电器能正常吸合释放之后再接电磁阀。接电磁阀的时候先关掉12V电源接好线再上电避免带电操作打火。测试通过之后把这段代码封装成一个类方便主程序调用class ValveController: def __init__(self, pin17): self.pin pin GPIO.setmode(GPIO.BCM) GPIO.setup(self.pin, GPIO.OUT) GPIO.output(self.pin, GPIO.HIGH) self.state off def open(self): GPIO.output(self.pin, GPIO.LOW) self.state on def close(self): GPIO.output(self.pin, GPIO.HIGH) self.state off def cleanup(self): self.close() GPIO.cleanup()3.3 MQTT客户端集成与主循环逻辑主程序的核心是一个循环读取传感器、检查天气、决策、执行、发布状态。我用paho-mqtt的loop_start()方法在后台线程跑MQTT网络循环主线程跑决策逻辑。import paho.mqtt.client as mqtt import json import time from datetime import datetime MQTT_BROKER localhost MQTT_PORT 1883 TOPIC_SOIL sprinqua/sensor/soil_moisture TOPIC_VALVE_STATE sprinqua/valve/state TOPIC_VALVE_CONTROL sprinqua/valve/control TOPIC_STATUS sprinqua/system/status client mqtt.Client() client.connect(MQTT_BROKER, MQTT_PORT, 60) client.loop_start() def on_message(client, userdata, msg): payload msg.payload.decode() if msg.topic TOPIC_VALVE_CONTROL: if payload on: valve.open() client.publish(TOPIC_VALVE_STATE, on, retainTrue) elif payload off: valve.close() client.publish(TOPIC_VALVE_STATE, off, retainTrue) client.on_message on_message client.subscribe(TOPIC_VALVE_CONTROL) valve ValveController(pin17) SOIL_THRESHOLD 30 RAIN_THRESHOLD 60 CHECK_INTERVAL 300 # 5分钟检查一次 MAX_IRRIGATION_TIME 600 # 单次最长浇水10分钟 while True: soil read_soil_moisture() rain_prob get_rain_probability(LAT, LON) client.publish(TOPIC_SOIL, f{soil:.1f}, retainTrue) if soil SOIL_THRESHOLD and rain_prob RAIN_THRESHOLD: valve.open() client.publish(TOPIC_VALVE_STATE, on, retainTrue) start_time time.time() while time.time() - start_time MAX_IRRIGATION_TIME: time.sleep(10) soil read_soil_moisture() if soil SOIL_THRESHOLD 10: break valve.close() client.publish(TOPIC_VALVE_STATE, off, retainTrue) status { uptime: time.time() - start_time, last_check: datetime.now().isoformat(), soil_moisture: soil, rain_probability: rain_prob, valve_state: valve.state } client.publish(TOPIC_STATUS, json.dumps(status), retainTrue) time.sleep(CHECK_INTERVAL)这段代码有几个关键点。第一retainTrue让MQTT Broker保留最后一条消息新订阅的客户端立刻能收到当前状态。第二浇水过程中每10秒检查一次土壤湿度如果湿度已经够了就提前关阀避免过度浇水。第三MAX_IRRIGATION_TIME是硬性保护防止传感器故障导致一直浇水。3.4 系统服务化与开机自启配置代码跑通之后需要把它做成systemd服务这样开机自动启动崩溃了也能自动重启。创建服务文件sudo nano /etc/systemd/system/sprinqua.service内容如下[Unit] DescriptionSprinqua Smart Irrigation Controller Afternetwork.target mosquitto.service [Service] Typesimple Userpi WorkingDirectory/home/pi/sprinqua ExecStart/usr/bin/python3 /home/pi/sprinqua/main.py Restartalways RestartSec10 [Install] WantedBymulti-user.target然后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable sprinqua sudo systemctl start sprinqua检查状态sudo systemctl status sprinqua如果看到active (running)就说明服务正常。查看日志journalctl -u sprinqua -f提示Restartalways和RestartSec10意味着程序崩溃后10秒会自动重启。但如果程序有bug导致反复崩溃会陷入重启循环。建议先用journalctl观察一段时间确认稳定后再长期运行。4. 常见问题与排查技巧实录4.1 继电器不动作或阀门状态异常这是最常见的问题排查思路从软到硬。先确认GPIO引脚编号对不对BCM编号和物理引脚编号是两回事。我见过有人把BCM17接到物理引脚17上结果控制的是另一个GPIO。用gpio readall命令可以查看所有引脚的状态。如果引脚没错用万用表测继电器输入端电压。低电平触发时GPIO输出LOW继电器输入端应该有接近0V的电压。如果电压不对可能是GPIO没有正确初始化或者被其他程序占用了。树莓派上有些GPIO有特殊功能比如I2C、SPI如果启用了这些功能对应的GPIO就不能当普通输出用了。继电器有“咔嗒”声但阀门不动作说明继电器本身工作正常问题在电磁阀回路。检查电磁阀电源是否接通用万用表测电磁阀两端有没有12V电压。如果没有检查继电器COM和NO的接线是否正确。如果有电压但阀门不动作可能是电磁阀坏了或者水压不够。还有一种情况是阀门状态在MQTT上显示不对。这通常是retain标志没设或者发布主题和订阅主题不一致。用mosquitto_sub -t sprinqua/# -v订阅所有相关主题看看实际发布的消息是什么。4.2 土壤湿度读数漂移或不准确电容式传感器虽然比电阻式耐用但长时间埋在土里也会受潮气影响导致读数漂移。我的做法是每两周把传感器拔出来擦干净重新校准一次。如果发现读数明显异常比如一直显示0%或100%先检查接线是否松动再检查MCP3008的SPI通信是否正常。SPI通信问题可以用ls /dev/spidev*查看设备节点是否存在。如果没有需要在raspi-config里启用SPI接口。另外MCP3008的VREF电压要稳定如果VREF波动读数也会波动。建议在VREF和GND之间加一个0.1uF的陶瓷电容滤波。还有一个容易被忽略的问题传感器插入土壤的深度。插得太浅读的是表层干土插得太深可能碰到花盆底部的积水层。一般插入土面以下5到10厘米比较合适具体看植物根系深度。4.3 MQTT连接断开或消息丢失MQTT连接断开通常有两个原因网络不稳定或Broker配置问题。如果树莓派和Broker在同一台机器上网络因素可以排除。检查Mosquitto的日志sudo tail -f /var/log/mosquitto/mosquitto.log如果看到Client sprinqua disconnected说明客户端主动断开了。paho-mqtt的loop_start()会在后台自动重连但重连后需要重新订阅主题。可以在on_connect回调里统一处理订阅def on_connect(client, userdata, flags, rc): client.subscribe(TOPIC_VALVE_CONTROL) client.on_connect on_connect消息丢失的情况如果QoS设为0消息可能丢失。对于控制指令建议用QoS 1确保至少送达一次。对于传感器数据QoS 0就够了丢一两条无所谓。注意如果局域网内有多个MQTT客户端主题命名要避免冲突。建议用项目名作为顶层主题比如sprinqua/这样不会和其他项目的主题混在一起。4.4 系统运行一段时间后无响应树莓派长时间运行后无响应最常见的原因是内存泄漏或SD卡故障。Python程序如果有循环引用或者未关闭的文件句柄内存会慢慢涨上去。用htop或者free -h查看内存占用如果发现Python进程内存持续增长就要检查代码里有没有未释放的资源。SD卡故障是树莓派的通病。频繁写日志会加速SD卡磨损。建议把日志写到内存文件系统或者限制日志大小。journalctl可以配置最大日志体积sudo journalctl --vacuum-size100M另外建议用高质量的SD卡或者干脆用USB SSD启动稳定性和寿命都好很多。我自己的树莓派跑了半年多SD卡就出现了坏块后来换了SSD再没出过问题。问题现象可能原因排查方法解决方案继电器不动作GPIO引脚错误gpio readall查看状态确认BCM编号阀门不动作电磁阀电源故障万用表测电压检查12V电源和接线湿度读数漂移传感器老化重新校准清洁或更换传感器MQTT断连网络或Broker问题查看Mosquitto日志配置自动重连和订阅系统无响应内存泄漏或SD卡故障htop和dmesg修复代码或换SSD4.5 冬季防冻与长期停用的处理如果你所在地区冬天会结冰灌溉系统必须做防冻处理。水管里的水结冰膨胀会把电磁阀和管子撑裂。我的做法是入冬前把水管里的水排空电磁阀拆下来存放在室内。树莓派可以继续跑但把灌溉功能禁用只保留传感器监测。在代码里加一个季节判断import datetime def is_winter(): month datetime.datetime.now().month return month in [11, 12, 1, 2, 3] if is_winter(): print(冬季模式跳过灌溉) time.sleep(CHECK_INTERVAL) continue这个逻辑很简单但很实用。如果你在南方冬天不结冰可以忽略这段。如果在北方这个判断能帮你省下换电磁阀的钱。4.6 扩展思路接入Home Assistant与语音控制系统跑通之后我把它接入了Home Assistant。HA有内置的MQTT集成配置好Broker地址后会自动发现sprinqua/下的实体。土壤湿度会显示成一个传感器卡片阀门状态显示成开关。你可以在HA的仪表盘上手动控制阀门也可以设置自动化比如“如果土壤湿度低于25%且太阳下山后开启灌溉10分钟”。语音控制是通过HA的语音助手实现的。对着音箱说“打开花园浇水”HA会向sprinqua/valve/control发布on阀门就打开了。这个体验很爽尤其是手上拿着东西不方便掏手机的时候。再进一步可以加一个流量传感器统计每次灌溉用了多少水。或者加一个雨量传感器直接测实际降雨量比天气预报更准。这些扩展都不难因为MQTT的发布订阅模型天生适合这种松耦合的架构。你加一个新传感器只需要让它往对应的主题发消息主程序订阅处理就行不用改现有的代码结构。我个人在实际操作中的体会是这套系统最大的价值不在于省了多少水或者多少时间而在于它让你对植物的生长环境有了量化的感知。以前浇水全凭感觉现在看着土壤湿度曲线什么时候该浇、浇了多少一目了然。这种掌控感是成品设备给不了的。
返回列表