公司动态

基于树莓派与Arduino的智能浇水系统:从传感器到云端的物联网实践

📅 2026/8/20 8:48:04
基于树莓派与Arduino的智能浇水系统:从传感器到云端的物联网实践
1. 项目概述从一盆枯萎的绿萝到WaterPi几年前我因为一次为期两周的出差回家后面对的是一盆彻底枯萎、救无可救的绿萝。作为一个热爱折腾硬件的开发者那种无力感促使我开始思考能不能做一个系统让我无论身在何处都能像在家一样照料我的植物这就是WaterPi项目的起点。它不仅仅是一个自动浇水装置更是一个完整的远程监控与决策系统核心目标是将种植经验数据化、操作远程化让植物养护变得智能且省心。简单来说WaterPi是一个基于树莓派Raspberry Pi和微控制器如Arduino或ESP32的物联网IoT项目。它通过传感器实时采集土壤湿度、环境温湿度、光照强度等关键数据经由树莓派处理后一方面可以在本地进行逻辑判断比如土壤太干就启动水泵另一方面将所有数据上传到云端例如AWS IoT供你通过网页或手机App远程查看。你可以在办公室的电脑前轻松查看家里龟背竹的“健康状况”并手动点击按钮为它补充水分。这个项目非常适合有一定动手能力的硬件爱好者、物联网初学者或者单纯是受够了植物“杀手”称号想用科技拯救绿植的朋友。它涉及的知识面较广包括硬件电路搭建、传感器驱动、嵌入式编程、服务器端开发以及前端界面设计是一个非常好的全栈实践项目。接下来我将拆解整个系统的设计思路、硬件选型、软件实现以及那些只有实际动手才会遇到的“坑”。2. 系统架构与核心组件选型一个可靠的远程浇水监控系统其架构设计决定了稳定性、可扩展性和成本。经过多次迭代我最终确定了分层式架构将感知、控制、计算和展示分离。2.1 整体架构设计思路系统主要分为三层感知控制层、边缘计算与网关层、云平台与应用层。感知控制层是系统的“手”和“感觉器官”部署在花盆旁边。它的核心是一个微控制器负责驱动各类传感器和执行器。我选择了Arduino Uno作为核心原因在于其生态成熟、引脚简单、编程容易非常适合快速原型开发。这一层需要完成以下任务数据采集周期性地读取土壤湿度传感器、温湿度传感器如DHT11/DHT22、光照传感器的模拟或数字信号。设备控制根据来自上层树莓派的指令或本地预设的简单逻辑控制继电器模块来开关水泵、补光灯等设备。初步处理与上报将采集到的原始数据进行简单的格式化然后通过串口UART通信发送给树莓派。边缘计算与网关层是系统的“大脑”和“通讯员”由树莓派担任。树莓派在这里扮演了多重角色协议转换网关接收Arduino通过串口发送的简单数据将其封装成符合MQTT等物联网协议的消息。边缘计算节点可以运行更复杂的判断逻辑。例如可以设置一个规则“如果连续3次检测到土壤湿度低于30%且环境温度高于28度则立即浇水5秒”。这种复杂规则放在树莓派上比放在Arduino上更易于管理和修改。本地数据缓存与转发将数据持久化到本地数据库如SQLite并在网络恢复后同步到云端防止数据丢失。本地服务提供者可以运行一个轻量的Web服务器提供局域网内的控制界面即使外网断开在家庭内部仍可操作。云平台与应用层是系统的“仪表盘”和“远程遥控器”。我选择了AWS IoT Core因为它提供了完善的设备管理、消息路由和安全认证机制。树莓派作为“设备”连接到AWS IoT将数据发布到指定的MQTT主题Topic。同时在AWS上可以部署一个无服务器架构使用AWS Lambda函数订阅MQTT主题处理传入的数据并将其存入Amazon DynamoDB或Timestream时序数据库。通过Amazon API Gateway提供RESTful API。前端可以是一个简单的React或Vue.js单页应用托管在Amazon S3上通过API获取数据并展示图表同时发送控制指令通过API Gateway触发Lambda再通过IoT Core下发到设备。注意对于个人项目云服务成本是需要考虑的因素。AWS IoT Core对于小流量应用有免费额度通常足够使用。你也可以选择其他物联网平台如阿里云物联网平台、腾讯云物联网开发平台或者使用开源的MQTT Broker如EMQX自建服务器但这需要你具备公网IP和一定的运维能力。2.2 硬件组件深度解析硬件是项目的基石选对组件事半功倍。主控单元对比Arduino Uno vs ESP32Arduino Uno经典入门选择。ATmega328P芯片5V工作电压数字和模拟I/O引脚足够本项目使用。其最大优势是稳定性高、社区资源极其丰富任何传感器几乎都有现成的库。缺点是本身不带网络功能需依赖树莓派联网。ESP32更现代的选择。双核处理器主频更高集成了Wi-Fi和蓝牙。这意味着你可以用一块ESP32直接连接Wi-Fi并上传数据到云端省去树莓派简化系统架构。但对于新手其配置环境Arduino IDE或PlatformIO和网络编程会稍复杂一些。我的选择与理由在WaterPi的当前架构中我依然推荐使用Arduino Uno。原因在于职责分离让Arduino专心、稳定地处理实时性要求高的传感器读取和IO控制让树莓派处理复杂的网络通信、逻辑判断和数据上行。这种分工降低了单个节点的复杂度提高了系统整体的可靠性。ESP32更适合作为一体化的精简方案。传感器选型与避坑指南土壤湿度传感器最常见的是电阻式传感器如FC-28。它通过两个探针间的电阻变化来检测湿度。这里有一个大坑如果一直通电插入土中探针会因电解作用而快速腐蚀寿命可能只有一两个月。解决方案有两种一是购买带有镀金探针的“电容式”土壤湿度传感器它通过检测电容变化来工作不与土壤直接发生电化学反应寿命更长二是即使使用电阻式也仅在测量时通过数字引脚为其供电测量完毕立即断电。温湿度传感器DHT11低成本精度一般或DHT22精度更高范围更广。它们使用单总线协议只需要一个数据引脚。注意要正确连接上拉电阻并且两次读取之间需要至少2秒的间隔。光照传感器BH1750是一个数字光照强度传感器使用I2C接口精度高使用方便直接输出lux值比光敏电阻更稳定。执行器与电源管理水泵与继电器小型潜水泵或直流隔膜泵12V即可。绝对不要直接用Arduino的5V引脚驱动水泵Arduino的IO引脚只能提供约40mA电流而水泵启动电流可能高达数百mA会瞬间烧毁芯片。必须使用继电器模块或MOS管驱动模块进行隔离控制。继电器模块相当于一个电子开关用Arduino的5V信号控制一个线圈来接通或断开水泵的12V供电电路。电源这是系统稳定的关键。Arduino、传感器、继电器模块的驱动部分可以用一个5V/2A的手机充电器供电。水泵则需要独立的12V电源适配器。务必确保电源功率充足特别是当水泵启动时电压跌落可能导致树莓派或Arduino重启。3. 下位机固件开发让Arduino“活”起来下位机Arduino的代码是整个系统的数据源头和控制终端其稳定性和准确性至关重要。3.1 传感器数据采集与滤波直接读取的传感器数据往往带有噪声需要进行软件滤波才能得到稳定值。// 示例读取土壤湿度并做滑动平均滤波 #define SOIL_SENSOR_PIN A0 #define POWER_PIN 7 // 控制传感器电源的引脚用于防腐蚀 const int numReadings 10; // 滤波窗口大小 int readings[numReadings]; int readIndex 0; int total 0; int average 0; void setup() { Serial.begin(9600); pinMode(POWER_PIN, OUTPUT); digitalWrite(POWER_PIN, LOW); // 初始断电 for (int thisReading 0; thisReading numReadings; thisReading) { readings[thisReading] 0; } } int readSoilMoisture() { digitalWrite(POWER_PIN, HIGH); // 给传感器供电 delay(20); // 等待传感器稳定非常重要 int rawValue analogRead(SOIL_SENSOR_PIN); digitalWrite(POWER_PIN, LOW); // 立即断电 // 将模拟值0-1023转换为百分比0-100%注意值越小越湿 // 具体映射关系需要根据传感器在空气中和水中的读数进行校准 int moisturePercent map(rawValue, AIR_VALUE, WATER_VALUE, 0, 100); moisturePercent constrain(moisturePercent, 0, 100); return moisturePercent; } void loop() { // 滑动平均滤波计算 total total - readings[readIndex]; // 减去最旧的读数 int currentMoisture readSoilMoisture(); readings[readIndex] currentMoisture; total total readings[readIndex]; // 加上最新的读数 readIndex readIndex 1; if (readIndex numReadings) { readIndex 0; } average total / numReadings; // 将平均后的数据通过串口发送出去格式化为JSON便于解析 Serial.print({\soil_moisture\:); Serial.print(average); Serial.print(,\temp\:); Serial.print(readDHTTemperature()); // 假设有读取DHT的函数 Serial.print(,\humidity\:); Serial.print(readDHTHumidity()); Serial.println(}); delay(5000); // 每5秒采集一次 }实操要点校准是关键AIR_VALUE和WATER_VALUE需要实测。将传感器探针完全置于空气中读取稳定的模拟值作为AIR_VALUE代表0%湿度将探针插入水中注意不要淹到电路部分读取值作为WATER_VALUE代表100%湿度。防腐蚀策略通过POWER_PIN控制传感器电源仅在测量时通电极大延长了电阻式传感器的寿命。通信协议使用JSON格式通过串口输出方便树莓派端的Node.js程序用JSON.parse()直接解析比自定义二进制协议或纯字符串分割更健壮。3.2 串口通信协议与指令解析树莓派不仅要接收数据还需要向Arduino发送控制指令如“浇水10秒”。我们需要定义一个简单的双向通信协议。// 在loop()函数中添加指令解析 void loop() { // ... 数据采集和发送代码 ... // 检查串口是否有指令传入 if (Serial.available() 0) { String command Serial.readStringUntil(\n); // 以换行符为命令结束符 command.trim(); if (command.startsWith(PUMP_ON:)) { int duration command.substring(8).toInt(); // 解析出时长如PUMP_ON:10 controlPump(duration); } else if (command.equals(GET_STATUS)) { // 立即返回一次所有传感器状态 reportImmediateStatus(); } } } void controlPump(int seconds) { digitalWrite(PUMP_RELAY_PIN, HIGH); // 打开继电器启动水泵 delay(seconds * 1000); // 注意这里使用delay会阻塞整个循环 digitalWrite(PUMP_RELAY_PIN, LOW); Serial.println({\event\:\pump_activated\, \duration\: String(seconds) }); }重要缺陷与改进上面的controlPump函数使用了delay()在浇水期间整个Arduino会卡住无法采集传感器数据或响应其他指令。这是必须避免的。正确的做法是使用非阻塞的定时方式例如利用millis()函数。unsigned long pumpStartTime 0; int pumpDuration 0; bool pumpRunning false; void controlPumpNonBlocking(int seconds) { pumpStartTime millis(); pumpDuration seconds * 1000; digitalWrite(PUMP_RELAY_PIN, HIGH); pumpRunning true; Serial.println({\event\:\pump_started\, \duration\: String(seconds) }); } void checkPumpStatus() { if (pumpRunning (millis() - pumpStartTime pumpDuration)) { digitalWrite(PUMP_RELAY_PIN, LOW); pumpRunning false; Serial.println({\event\:\pump_stopped\}); } } // 在loop()中用checkPumpStatus()替换delay void loop() { // ... 数据采集 ... checkPumpStatus(); // 非阻塞检查水泵是否该关闭 // ... 指令解析 ... }4. 网关与云端桥梁树莓派上的Node.js服务树莓派运行Raspbian系统我们使用Node.js来编写网关程序。选择Node.js是因为其异步非阻塞特性非常适合处理I/O密集型的网络通信和串口数据读取。4.1 环境搭建与核心依赖首先在树莓派上安装Node.js。建议使用NodeSource的仓库安装LTS版本。# 安装Node.js curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs # 验证安装 node --version npm --version创建一个项目目录并初始化安装必要的npm包mkdir waterpi-gateway cd waterpi-gateway npm init -y npm install serialport aws-iot-device-sdk-v2 dotenvserialport: 用于与Arduino进行串口通信。aws-iot-device-sdk-v2: AWS IoT设备SDK用于连接和认证AWS IoT Core。dotenv: 管理环境变量将敏感信息如证书路径从代码中分离。4.2 串口数据读取与解析编写一个serialReader.js模块来稳定地读取和解析Arduino发来的JSON数据。const { SerialPort } require(serialport); const { ReadlineParser } require(serialport/parser-readline); class SerialReader { constructor(portPath /dev/ttyACM0, baudRate 9600) { this.port new SerialPort({ path: portPath, baudRate: baudRate }); const parser this.port.pipe(new ReadlineParser({ delimiter: \n })); parser.on(data, (line) { try { const data JSON.parse(line.trim()); console.log(Parsed sensor data:, data); // 触发一个事件让主程序可以处理这些数据 this.emit(data, data); } catch (error) { console.error(Failed to parse JSON from serial:, line, error); // 可能是数据不完整或乱码可以选择忽略或记录 } }); this.port.on(error, (err) { console.error(Serial port error:, err.message); this.emit(error, err); }); this.port.on(open, () { console.log(Serial port ${portPath} opened.); }); } // 发送指令到Arduino sendCommand(command) { if (this.port.isOpen) { this.port.write(command \n); console.log(Command sent: ${command}); } else { console.error(Serial port is not open.); } } } // 使用EventEmitter让类可以触发事件 const EventEmitter require(events); Object.assign(SerialReader.prototype, EventEmitter.prototype); module.exports SerialReader;注意事项串口设备路径树莓派上Arduino通常连接为/dev/ttyACM0或/dev/ttyUSB0。如果连接不上可以使用ls /dev/tty*命令查看。权限问题普通用户可能无权访问串口设备。可以将用户加入dialout组sudo usermod -a -G dialout $USER然后需要重新登录生效。错误处理串口通信易受干扰必须做好try...catch和错误监听防止程序因单次解析失败而崩溃。4.3 连接AWS IoT Core并发布数据在AWS IoT控制台创建设备“事物”Thing并下载其连接证书设备证书、私钥和根CA证书。将这些文件安全地存放在树莓派上。创建一个awsPublisher.js模块const awsIot require(aws-iot-device-sdk-v2); const iot awsIot.iot; const mqtt awsIot.mqtt; class AwsIoTPublisher { constructor(config) { const clientConfig { keyPath: config.keyPath, // 私钥文件路径 certPath: config.certPath, // 设备证书文件路径 caPath: config.caPath, // 根CA证书路径 clientId: config.clientId, // 设备客户端ID需唯一 host: config.host, // AWS IoT端点形如 xxxxxx-ats.iot.region.amazonaws.com }; this.client mqtt.ClientConnection iot.MqttClient.newClient(clientConfig); this.topic waterpi/${config.clientId}/sensor/data; // 发布数据的主题 this.client.on(connect, () { console.log(Connected to AWS IoT Core); this.connected true; }); this.client.on(error, (error) { console.error(AWS IoT Connection error:, error); this.connected false; }); this.client.on(interrupt, (error) { console.log(Connection interrupted:, error); }); this.client.on(resume, (returnCode, sessionPresent) { console.log(Connection resumed:, returnCode, sessionPresent); }); } async connect() { try { const connection await this.client.connect(); console.log(MQTT connection established.); return connection; } catch (err) { console.error(Failed to connect:, err); throw err; } } publishData(data) { if (!this.connected) { console.warn(Not connected to AWS IoT, skipping publish.); return; } const payload JSON.stringify({ ...data, timestamp: new Date().toISOString(), // 添加服务器端时间戳 }); this.client.publish(this.topic, payload, mqtt.QoS.AtLeastOnce, (error) { if (error) { console.error(Publish error:, error); } else { console.log(Data published to topic:, this.topic); } }); } // 订阅控制主题接收来自云端的指令 subscribeToControlTopic(topic, callback) { this.client.subscribe(topic, mqtt.QoS.AtLeastOnce, (error, granted) { if (error) { console.error(Subscribe error:, error); } else { console.log(Subscribed to topic:, granted[0].topic); this.client.on(message, (receivedTopic, payload) { if (receivedTopic topic) { const message payload.toString(); callback(message); } }); } }); } } module.exports AwsIoTPublisher;在主程序index.js中整合这两个模块require(dotenv).config(); const SerialReader require(./serialReader); const AwsIoTPublisher require(./awsPublisher); const serialReader new SerialReader(process.env.SERIAL_PORT, 9600); const publisher new AwsIoTPublisher({ keyPath: process.env.AWS_KEY_PATH, certPath: process.env.AWS_CERT_PATH, caPath: process.env.AWS_CA_PATH, clientId: process.env.AWS_CLIENT_ID, host: process.env.AWS_IOT_ENDPOINT, }); (async () { try { await publisher.connect(); // 订阅控制主题例如 waterpi/device001/control publisher.subscribeToControlTopic(waterpi/${process.env.AWS_CLIENT_ID}/control, (message) { console.log(Control command received:, message); // 解析指令并转发给Arduino serialReader.sendCommand(message); }); // 当串口收到数据时发布到AWS IoT serialReader.on(data, (sensorData) { publisher.publishData(sensorData); }); serialReader.on(error, (err) { console.error(Serial reader error, attempting to reconnect..., err); // 可以实现重连逻辑 }); } catch (error) { console.error(Initialization failed:, error); process.exit(1); } })(); // 优雅退出 process.on(SIGINT, () { console.log(Shutting down...); // 关闭串口和MQTT连接 process.exit(0); });环境变量文件.env示例SERIAL_PORT/dev/ttyACM0 AWS_KEY_PATH/home/pi/certs/private.pem.key AWS_CERT_PATH/home/pi/certs/certificate.pem.crt AWS_CA_PATH/home/pi/certs/AmazonRootCA1.pem AWS_CLIENT_IDwaterpi_raspberry_01 AWS_IOT_ENDPOINTxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com5. 云端逻辑与前端展示数据上了云我们就可以做更多事情持久化存储、复杂分析、远程控制以及可视化。5.1 AWS IoT规则引擎与数据流转在AWS IoT控制台我们可以创建一条规则Rule将设备发布到sensor/data主题的消息自动转发到其他服务进行处理。一个典型的规则动作可以是存入DynamoDB用于记录最新的传感器状态便于快速查询。存入Timestream这是一个专门为时序数据优化的数据库非常适合存储带时间戳的传感器读数用于绘制历史趋势图。触发Lambda函数执行更复杂的业务逻辑。例如检查土壤湿度是否低于阈值如果是则通过IoT Core向设备下发浇水指令或者向你的手机发送一条SNS通知。创建规则的SQL语句示例在IoT控制台规则查询语句中SELECT state.reported.soil_moisture as moisture, state.reported.temp as temperature, state.reported.humidity as humidity, timestamp() as timestamp, clientid() as deviceId FROM waterpi//sensor/data这条规则会监听所有匹配waterpi//sensor/data主题的消息是通配符并提取出有用的字段。5.2 构建简易监控Web界面前端界面可以直接托管在AWS S3上实现静态网站托管。使用Chart.js来绘制温湿度和土壤湿度的历史曲线并添加一个手动浇水的按钮。一个简化的HTML/JS示例 (index.html)!DOCTYPE html html head titleWaterPi Dashboard/title script srchttps://cdn.jsdelivr.net/npm/chart.js/script script srchttps://cdn.jsdelivr.net/npm/axios/dist/axios.min.js/script /head body h1我的植物状态/h1 div p土壤湿度: span idmoisture--/span%/p p温度: span idtemperature--/span°C/p p湿度: span idhumidity--/span%/p /div div button onclickwaterPlant(5)浇水5秒/button button onclickwaterPlant(10)浇水10秒/button /div canvas idsensorChart width800 height400/canvas script const apiBaseUrl https://your-api-gateway-url.amazonaws.com/prod; // 替换为你的API网关地址 const deviceId waterpi_raspberry_01; let sensorChart; async function fetchLatestData() { try { const response await axios.get(${apiBaseUrl}/sensor/latest?deviceId${deviceId}); const data response.data; document.getElementById(moisture).textContent data.moisture; document.getElementById(temperature).textContent data.temperature; document.getElementById(humidity).textContent data.humidity; updateChart(data); // 假设这个函数会更新图表 } catch (error) { console.error(Failed to fetch data:, error); } } async function waterPlant(seconds) { try { await axios.post(${apiBaseUrl}/control/water, { deviceId: deviceId, duration: seconds }); alert(已发送浇水${seconds}秒指令); } catch (error) { console.error(Failed to send command:, error); alert(指令发送失败); } } // 初始化图表和定时刷新 function initChart() { const ctx document.getElementById(sensorChart).getContext(2d); sensorChart new Chart(ctx, { type: line, data: { labels: [], // 时间标签 datasets: [{ label: 土壤湿度 (%), data: [], borderColor: rgb(75, 192, 192), tension: 0.1 }, { label: 温度 (°C), data: [], borderColor: rgb(255, 99, 132), tension: 0.1 }] }, options: { responsive: true } }); } function updateChart(newDataPoint) { // 简化逻辑将新数据点添加到图表中 const now new Date().toLocaleTimeString(); sensorChart.data.labels.push(now); sensorChart.data.datasets[0].data.push(newDataPoint.moisture); sensorChart.data.datasets[1].data.push(newDataPoint.temperature); // 只保留最近20个数据点 if (sensorChart.data.labels.length 20) { sensorChart.data.labels.shift(); sensorChart.data.datasets.forEach(dataset dataset.data.shift()); } sensorChart.update(); } window.onload function() { initChart(); fetchLatestData(); setInterval(fetchLatestData, 10000); // 每10秒刷新一次数据 }; /script /body /html后端API可以通过API Gateway Lambda来实现。Lambda函数从DynamoDB查询最新数据或者向一个控制主题发布MQTT消息来触发浇水。6. 系统部署、优化与故障排查将代码写好只是第一步让系统7x24小时稳定运行才是真正的挑战。6.1 树莓派生产环境部署不要让Node.js程序在前台运行使用进程管理工具pm2。# 全局安装pm2 sudo npm install -g pm2 # 在项目目录下用pm2启动你的网关应用 pm2 start index.js --name waterpi-gateway # 设置开机自启动 pm2 startup # 执行pm2给出的命令然后保存当前进程列表 pm2 save系统服务化更进一步可以创建一个systemd服务文件管理得更规范。6.2 电源与连接稳定性优化电源问题树莓派对电源非常敏感。使用官方电源或质量可靠的5V/3A电源适配器。供电不足会导致树莓派重启、网络断开或USB设备识别异常这是许多奇怪问题的根源。看门狗与自动重启编写一个简单的“看门狗”脚本定期检查网关进程和串口连接是否正常如果异常则自动重启服务甚至重启树莓派。网络重连在Node.js的AWS IoT SDK中已经内置了断线重连机制。但你需要确保树莓派的Wi-Fi连接稳定。可以考虑使用connman或dhcpcd的配置来增强Wi-Fi稳定性或者直接使用有线网络。6.3 常见问题排查实录问题串口读取不到数据或者数据是乱码。排查检查串口设备路径是否正确 (ls /dev/tty*)。检查串口波特率是否与Arduino代码中设置的Serial.begin(9600)一致。使用sudo screen /dev/ttyACM0 9600命令直接连接串口看能否看到Arduino打印的原始数据。这是最直接的调试方法。检查接地GND。确保树莓派和Arduino的GND引脚连接在一起共地是通信的基础。问题AWS IoT连接失败提示证书或策略错误。排查确认证书文件路径正确且Node.js进程有读取权限。在AWS IoT控制台检查该“事物”的证书是否已激活Active。检查该证书附加的策略Policy是否正确。一个最小化的策略示例如下{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: iot:Connect, Resource: arn:aws:iot:region:account:client/${iot:ClientId} }, { Effect: Allow, Action: iot:Publish, Resource: arn:aws:iot:region:account:topic/waterpi/${iot:ClientId}/* }, { Effect: Allow, Action: iot:Subscribe, Resource: arn:aws:iot:region:account:topicfilter/waterpi/${iot:ClientId}/* }, { Effect: Allow, Action: iot:Receive, Resource: arn:aws:iot:region:account:topic/waterpi/${iot:ClientId}/* } ] }检查AWS IoT终端节点Endpoint是否正确不同区域端点不同。问题水泵不工作但继电器有“咔嗒”声。排查最常见原因水泵电源功率不足或未接。用万用表测量水泵两端的电压在继电器吸合时是否达到12V。检查继电器模块的接线控制端IN是否接对了Arduino引脚常开端NO和公共端COM是否串接在水泵的电源回路中。水泵本身是否损坏可以直接将水泵接上12V电源测试。问题土壤湿度读数不准或者一直不变。排查校准校准校准这是最重要的一步。空气中的值和水中的值必须重新校准特别是换了一种土或传感器后。检查传感器探针是否与土壤接触良好。过于松散的土壤可能导致接触电阻不稳定。如果使用防腐蚀方案间歇供电确保供电后等待了足够的时间delay(20)再读取让传感器电路稳定。这个项目从构思到稳定运行我花了差不多两个月的时间期间最大的收获不是代码或硬件本身而是对“可靠性”的理解。硬件项目与纯软件不同一个松动的接头、一个不稳定的电源都可能让整个系统瘫痪。我的建议是分模块测试每连接一个传感器就写一段代码验证它最后再整合。当你在手机上第一次看到家里植物真实的土壤湿度曲线时那种成就感远超乎想象。现在我的绿萝再也没枯过而我的阳台已经变成了一个小型的、自给自足的智能绿洲。你可以从控制一个花盆开始然后扩展到整个阳台的灌溉系统甚至加入摄像头做图像识别判断植物是否生病。乐趣才刚刚开始。