十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于reTerminal的智能种植系统:物联网边缘计算实践

基于reTerminal的智能种植系统:物联网边缘计算实践 1. 项目缘起为什么要在reTerminal上搞“迷你农场”最近几年边缘计算和物联网设备越来越火各种小巧但功能强大的开发板层出不穷。我手头有一块来自Seeed Studio的reTerminal这块板子挺有意思它集成了一个5英寸的触摸屏、树莓派CM4计算模块、丰富的传感器包括光感、温湿度、六轴IMU和一堆GPIO接口。拿到手之后除了跑跑Demo我一直在琢磨能不能用它干点更有趣、更“接地气”的事。于是“MiniFarm on reTerminal”这个想法就诞生了。本质上我想把它变成一个集监控、控制、数据可视化于一体的微型智能种植终端。你可能会问市面上不是有很多现成的智能花盆吗没错但那些大多是封闭系统功能固定可玩性不高。而用reTerminal自己搭建你可以完全自定义监控的指标比如土壤湿度、光照强度、环境温湿度、设定复杂的自动化规则例如“当土壤湿度低于30%且光照充足时自动浇水10秒”更重要的是那块5英寸的屏幕可以实时、直观地展示所有数据和植物状态这比手机APP上的一个图标要生动得多。这个项目非常适合那些对物联网、Python编程和植物种植都感兴趣的开发者或爱好者。它不要求你有深厚的嵌入式开发背景因为树莓派生态的友好性大大降低了门槛。通过这个项目你不仅能收获一盆或几盆被精心照料的植物更能亲手实践从传感器数据采集、逻辑判断到执行器控制、再到本地UI显示的完整物联网闭环理解“边缘智能”的实际含义。2. 硬件选型与连接打造迷你农场的“感官”和“手脚”reTerminal本身已经是一个功能相当齐全的核心了但要构建一个农场我们还需要为它扩展“感官”传感器和“手脚”执行器。我的核心需求是监测植物最关键的几个环境参数并实现基础的自动补水。2.1 传感器扩展感知土壤与环境的秘密reTerminal板载了BME280温湿度气压传感器和LTR-553ALS-WA光线传感器这已经解决了空气温湿度和光照强度的监测问题。但植物生长还有一个至关重要的指标土壤湿度。为此我选择了一款常见的电容式土壤湿度传感器。与传统的电阻式传感器相比电容式传感器通过检测土壤的介电常数来推算湿度其金属探头不易电解腐蚀寿命更长更适合长期埋土使用。连接非常简单。电容式土壤湿度传感器一般输出的是模拟信号0-3.3V或0-5V而reTerminal的GPIO通过其40针扩展接口引出主要是数字IO。因此我们需要一个ADC模数转换器模块作为桥梁。我选用的是ADS1115一款高精度、16位、4通道的I2C接口ADC模块其精度远高于树莓派内置的ADC如果有的话能更准确地反映土壤湿度的细微变化。接线步骤如下供电将ADS1115模块的VDD引脚连接到reTerminal扩展口的3.3V引脚GND连接到GND。数据通信将ADS1115的SCL和SDA引脚分别连接到reTerminal扩展口的I2C1时钟线GPIO3/SCL和I2C1数据线GPIO2/SDA。传感器连接将土壤湿度传感器的VCC和GND连接到ADS1115模块提供的3.3V和GND注意共地。将传感器的信号输出线AO连接到ADS1115的A0通道或其他任意空闲通道。注意有些土壤湿度传感器模块也提供了数字输出口DO通过一个电位器设定阈值当湿度超过阈值时输出高/低电平。这种用法更简单但失去了连续监测的能力无法在UI上显示具体的湿度百分比曲线。因此我坚持使用模拟接口ADC的方案。2.2 执行器控制赋予系统行动力“农场”需要自动化照料最基础的就是自动浇水。我选择了一个5V的小型潜水泵并通过一个继电器模块来控制它的通断。为什么用继电器因为reTerminal的GPIO引脚只能输出3.3V的数字信号驱动能力电流很小无法直接驱动水泵这种功率较大的感性负载。继电器模块相当于一个用微弱信号来自GPIO控制大电流电路水泵电源的电子开关。接线步骤继电器模块将其VCC和GND连接到reTerminal扩展口的5V和GND引脚。将控制端IN或类似标识连接到任意一个GPIO例如GPIO17。水泵将水泵的电源正极红线连接到继电器模块的常开NO端子负极黑线直接连接到电源负极。然后将一个外部5V电源如手机充电器适配器的正极连接到继电器模块的公共COM端子负极与水泵电源负极并接。这样当我们在程序中设置GPIO17为高电平时继电器吸合COM与NO接通外部5V电源为水泵供电开始浇水设置为低电平时继电器断开水泵停止工作。实操心得务必为水泵这类电机负载单独供电不要试图从reTerminal的GPIO或5V引脚取电。电机启动瞬间的电流冲击很可能导致reTerminal重启或损坏。使用继电器隔离控制信号和动力电源是最稳妥的做法。3. 软件架构搭建从数据到决策的流水线硬件连接好后我们需要一套软件来让整个系统“活”起来。我的软件架构基于Python分为三个层次驱动层、逻辑层和表示层。这种分层设计使得代码结构清晰易于维护和扩展。3.1 驱动层与硬件对话这一层负责直接操作硬件读取传感器数据和控制执行器。我们需要安装几个关键的Python库。首先通过SSH登录到reTerminal它运行的是基于树莓派OS的系统进行基础安装和配置# 更新系统 sudo apt update sudo apt upgrade -y # 安装Python3包管理工具pip如果尚未安装 sudo apt install python3-pip -y # 启用I2C接口用于ADS1115 sudo raspi-config # 在界面中选择 Interface Options - I2C - Yes 启用然后重启。然后安装必要的Python库pip3 install smbus2 # 用于I2C通信 pip3 install adafruit-circuitpython-ads1x15 # ADS1115驱动库 pip3 install Pillow # 图像处理库用于在屏幕上绘图 pip3 install gpiozero # 操作GPIO的友好库gpiozero库是对底层RPi.GPIO库的封装语法更简洁直观非常适合初学者。接下来创建驱动脚本sensors_actuators.pyimport time import board import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn from gpiozero import OutputDevice import smbus2 # 初始化I2C总线 i2c busio.I2C(board.SCL, board.SDA) # 初始化ADS1115 ads ADS.ADS1115(i2c) # 配置增益这里选择±4.096V量程适用于3.3V传感器 ads.gain 1 # 创建A0通道的模拟输入对象 soil_sensor AnalogIn(ads, ADS.P0) # 初始化继电器控制引脚 (GPIO17) water_pump_relay OutputDevice(17, active_highTrue, initial_valueFalse) # 读取板载BME280传感器 (通过smbus) def read_bme280(): # BME280的I2C地址通常是0x76或0x77reTerminal上一般是0x76 bus smbus2.SMBus(1) address 0x76 # 这里需要根据BME280的数据手册编写具体的读取和计算代码 # 为简化示例我们返回模拟数据 # 实际项目中应使用专门的bme280库如pip3 install RPi.bme280 temperature 25.0 # 模拟值 humidity 60.0 # 模拟值 return temperature, humidity # 读取土壤湿度 def read_soil_moisture(): # ADS1115是16位ADC量程取决于增益设置。 # 传感器输出0-3.3V对应ADC值0-32767 (因为ads1115是16位有符号但我们用单端输入所以是0-32767) # 土壤越湿电压越高电容值越大。但具体数值需要校准。 raw_value soil_sensor.value voltage soil_sensor.voltage # 简易转换假设传感器在空气中最干输出1.0V在水中最湿输出3.0V # 湿度百分比 (当前电压 - 干燥电压) / (湿润电压 - 干燥电压) * 100% dry_voltage 1.0 wet_voltage 3.0 moisture_percent max(0, min(100, (voltage - dry_voltage) / (wet_voltage - dry_voltage) * 100)) return moisture_percent, raw_value, voltage # 控制水泵 def control_water_pump(state): 控制水泵开关 Args: state (bool): True 开 False 关 water_pump_relay.value state return water_pump_relay.value关键点解析土壤湿度的百分比转换是一个需要校准的过程。上面的dry_voltage和wet_voltage是示例值。你需要将传感器完全置于干燥空气中读取电压作为dry_voltage再将其完全浸入水中读取电压作为wet_voltage。实际种植中对于不同的土壤类型这两个值也会有差异可能需要根据植物状态进行微调。3.2 逻辑层制定灌溉策略逻辑层是系统的大脑它根据驱动层采集的数据按照我们设定的规则决定是否浇水、浇多久。我将其实现为一个独立的类在logic_engine.py中。import time from datetime import datetime class IrrigationLogic: def __init__(self): self.moisture_threshold 30.0 # 土壤湿度低于30%时触发浇水 self.pump_duration 5.0 # 每次浇水持续时间秒 self.cooldown_period 3600 # 两次浇水之间的最小间隔秒防止频繁启动 self.last_water_time 0 # 上次浇水的时间戳 self.is_watering False self.watering_start_time 0 def evaluate(self, current_moisture, current_timeNone): 评估当前状态返回是否需要浇水以及浇水状态 if current_time is None: current_time time.time() decision { should_water: False, reason: , is_watering: self.is_watering } # 如果正在浇水检查是否到时间停止 if self.is_watering: if current_time - self.watering_start_time self.pump_duration: decision[should_water] False decision[reason] watering_complete self.is_watering False self.last_water_time current_time else: decision[should_water] True decision[reason] watering_in_progress return decision # 不在浇水状态判断是否满足新一次浇水条件 # 条件1土壤湿度低于阈值 condition1 current_moisture self.moisture_threshold # 条件2距离上次浇水已过冷却时间 condition2 (current_time - self.last_water_time) self.cooldown_period if condition1 and condition2: decision[should_water] True decision[reason] moisture_low self.is_watering True self.watering_start_time current_time elif condition1 and not condition2: decision[reason] moisture_low_but_in_cooldown elif not condition1: decision[reason] moisture_adequate return decision def manual_override(self, command): 手动控制开关 Args: command (str): ON 或 OFF if command ON and not self.is_watering: self.is_watering True self.watering_start_time time.time() return True, 手动浇水开始 elif command OFF and self.is_watering: self.is_watering False self.last_water_time time.time() return True, 手动浇水停止 else: return False, 状态未改变或命令无效这个逻辑引擎非常基础但有效。它引入了“冷却期”的概念防止传感器读数在浇水后短暂波动导致水泵频繁启停。你可以很容易地扩展它例如加入“仅在白天浇水”结合光照传感器数据、“根据温度调整浇水量”等更复杂的规则。3.3 表示层在reTerminal屏幕上绘制监控仪表盘这是最有趣的部分我们要在reTerminal的5英寸屏幕上创建一个直观的图形界面。由于不需要复杂的交互主要是显示和几个按钮我选择了Python的PILPillow库直接在屏幕上绘图配合gpiozero读取板载按键这样足够轻量且高效。首先需要确保系统可以访问帧缓冲设备framebuffer来直接绘图。reTerminal的屏幕通常被配置为控制台或由窗口管理器使用。一种简单的方法是使用Linux的帧缓冲设备/dev/fb0。创建dashboard.pyimport time import threading from PIL import Image, ImageDraw, ImageFont import numpy as np import os # 尝试导入驱动层和逻辑层 try: from sensors_actuators import read_soil_moisture, read_bme280, control_water_pump from logic_engine import IrrigationLogic except ImportError: # 用于测试的模拟数据 def read_soil_moisture(): import random return random.uniform(20, 80), 0, 0 def read_bme280(): import random return random.uniform(20, 30), random.uniform(40, 80) def control_water_pump(state): print(f[模拟] 水泵: {开 if state else 关}) return state class IrrigationLogic: def __init__(self): self.moisture_threshold 30.0 def evaluate(self, moisture, current_timeNone): import random reasons [moisture_low, moisture_adequate, in_cooldown] return {should_water: random.choice([True, False]), reason: random.choice(reasons), is_watering: False} class Dashboard: def __init__(self, fb_device/dev/fb0): self.fb_device fb_device self.screen_size (800, 480) # reTerminal屏幕分辨率 self.logic IrrigationLogic() self.data_history {moisture: [], temp: [], humi: []} self.history_length 50 self.running True self.font_large ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf, 32) self.font_medium ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 24) self.font_small ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 18) # 颜色定义 self.colors { bg: (40, 44, 52), card: (50, 54, 63), text: (220, 220, 220), good: (120, 200, 150), warning: (255, 180, 100), alert: (255, 120, 120), pump_on: (100, 180, 255), } def _draw_gauge(self, draw, center, radius, value, label, unit, min_val0, max_val100): 绘制一个圆形仪表 x, y center # 绘制背景圆环 draw.ellipse([x-radius, y-radius, xradius, yradius], outlineself.colors[card], width3) # 计算圆弧角度 (0°在顶部顺时针) start_angle -90 end_angle start_angle (value / max_val) * 360 # 绘制值弧 draw.arc([x-radius, y-radius, xradius, yradius], start_angle, end_angle, fillself.colors[good], width8) # 绘制文本 text f{value:.1f}{unit} bbox draw.textbbox((0,0), text, fontself.font_medium) text_w, text_h bbox[2]-bbox[0], bbox[3]-bbox[1] draw.text((x-text_w/2, y-text_h/2), text, fontself.font_medium, fillself.colors[text]) # 绘制标签 bbox_label draw.textbbox((0,0), label, fontself.font_small) label_w bbox_label[2]-bbox_label[0] draw.text((x-label_w/2, yradius10), label, fontself.font_small, fillself.colors[text]) def _draw_history(self, draw, data_list, position, size, color, label): 绘制历史数据曲线 sx, sy position w, h size if len(data_list) 2: return # 归一化数据到绘图区域 points [] min_v, max_v min(data_list), max(data_list) range_v max_v - min_v if max_v min_v else 1 for i, val in enumerate(data_list): x sx (i / (len(data_list)-1)) * w y sy h - ((val - min_v) / range_v) * h points.append((x, y)) # 绘制曲线 if len(points) 1: draw.line(points, fillcolor, width2) # 绘制边框和标签 draw.rectangle([sx, sy, sxw, syh], outlineself.colors[card], width1) draw.text((sx, sy-25), label, fontself.font_small, fillcolor) def update_display(self, soil_moisture, temperature, humidity, pump_state, logic_reason): 更新并渲染整个仪表盘到屏幕 # 更新历史数据 for lst, val in zip([self.data_history[moisture], self.data_history[temp], self.data_history[humi]], [soil_moisture, temperature, humidity]): lst.append(val) if len(lst) self.history_length: lst.pop(0) # 创建新图像 img Image.new(RGB, self.screen_size, colorself.colors[bg]) draw ImageDraw.Draw(img) # 1. 绘制标题 draw.text((20, 10), MiniFarm Dashboard, fontself.font_large, fillself.colors[text]) # 2. 绘制仪表区域 (上半部分) self._draw_gauge(draw, (150, 150), 80, soil_moisture, Soil Moisture, %) self._draw_gauge(draw, (400, 150), 80, temperature, Temperature, °C) self._draw_gauge(draw, (650, 150), 80, humidity, Humidity, %) # 3. 绘制状态栏 status_y 250 pump_color self.colors[pump_on] if pump_state else self.colors[text] pump_text PUMP: ON if pump_state else PUMP: OFF draw.text((20, status_y), pump_text, fontself.font_medium, fillpump_color) draw.text((250, status_y), fLogic: {logic_reason}, fontself.font_small, fillself.colors[text]) # 4. 绘制历史曲线区域 (下半部分) history_start_y 300 graph_h 150 self._draw_history(draw, self.data_history[moisture], (20, history_start_y), (760, graph_h), self.colors[good], Moisture Trend) # 可以在同一区域叠加其他曲线或分区域绘制 # 5. 将图像写入帧缓冲设备 try: # 将PIL图像转换为RGB字节流并写入fb0 rgb_img img.convert(RGB) pixel_data rgb_img.tobytes() with open(self.fb_device, wb) as fb: fb.write(pixel_data) except Exception as e: print(f写入帧缓冲失败: {e}) # 作为后备方案保存为图片文件用于调试 img.save(/tmp/dashboard_debug.png) def run(self): 主循环 print(MiniFarm Dashboard 启动...) try: while self.running: # 读取传感器数据 soil_moisture, _, _ read_soil_moisture() temperature, humidity read_bme280() # 执行逻辑判断 decision self.logic.evaluate(soil_moisture, time.time()) pump_state decision[should_water] # 控制水泵 current_pump_state control_water_pump(pump_state) # 更新显示 self.update_display(soil_moisture, temperature, humidity, current_pump_state, decision[reason]) # 控制刷新率例如2秒一次 time.sleep(2) except KeyboardInterrupt: print(\n正在关闭...) finally: control_water_pump(False) # 确保水泵关闭 self.running False if __name__ __main__: dashboard Dashboard() dashboard.run()这个仪表盘实现了实时数据显示圆形仪表、历史趋势曲线底部和系统状态提示。由于直接写入帧缓冲它非常高效且不依赖任何桌面环境。踩坑实录直接写入/dev/fb0可能会与系统已有的图形界面如LXDE冲突导致显示异常或无法写入。有两种解决方案1) 将系统设置为控制台模式不启动图形界面。2) 使用更高级的图形库如pygame或tkinter在窗口管理器中创建应用。为了极简和性能我选择了方案一通过sudo systemctl set-default multi-user.target并重启让系统启动到命令行模式。这样我们的Python脚本就能独占屏幕了。4. 系统集成与后台服务化让农场7x24小时运行我们当然不希望每次启动农场都要手动SSH进去运行Python脚本。我们需要将其设置为一个系统服务开机自启并在后台稳定运行。4.1 创建主应用程序脚本将之前的驱动层、逻辑层和表示层整合到一个主脚本中例如minifarm_main.py。这个脚本包含所有初始化代码和主循环。#!/usr/bin/env python3 MiniFarm on reTerminal - 主应用程序 import time import signal import sys from dashboard import Dashboard def signal_handler(sig, frame): print(收到退出信号正在清理...) sys.exit(0) if __name__ __main__: signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) print(启动 MiniFarm 系统...) dashboard Dashboard() try: dashboard.run() except Exception as e: print(f主循环发生错误: {e}) finally: print(MiniFarm 系统已停止。)记得给脚本添加执行权限chmod x minifarm_main.py4.2 创建Systemd服务单元文件这是让程序在Linux后台作为服务运行的标准方法。创建一个服务文件sudo nano /etc/systemd/system/minifarm.service[Unit] DescriptionMiniFarm on reTerminal Service Afternetwork.target multi-user.target Wantsnetwork.target [Service] Typesimple Userpi # 替换为你的用户名reTerminal默认用户可能是pi或seeed WorkingDirectory/home/pi/minifarm # 替换为你的项目目录绝对路径 ExecStart/usr/bin/python3 /home/pi/minifarm/minifarm_main.py Restarton-failure RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target关键配置解析Typesimple: 我们的主程序是前台运行的。User: 指定运行服务的用户避免使用root。WorkingDirectory: 设置工作目录确保脚本内的相对路径如读取文件能正常工作。Restarton-failure: 程序意外退出时自动重启增加稳定性。RestartSec10: 重启前等待10秒避免频繁重启循环。4.3 启用并启动服务# 重新加载systemd配置 sudo systemctl daemon-reload # 启用开机自启 sudo systemctl enable minifarm.service # 立即启动服务 sudo systemctl start minifarm.service # 查看服务状态和日志 sudo systemctl status minifarm.service sudo journalctl -u minifarm.service -f # 实时查看日志现在你的MiniFarm就已经在后台默默运行了。即使你关闭SSH会话甚至重启reTerminal它都会自动启动并开始工作。5. 功能扩展与优化思路让农场更智能基础版本已经能工作但还有很多可以完善和扩展的地方这正是DIY项目的乐趣所在。5.1 增加更多传感器土壤EC/pH传感器监测土壤肥力和酸碱度适合种植对土壤要求高的植物。水位传感器监测储水容器的水位在水位过低时通过屏幕或网络通知你加水。摄像头模块通过树莓派CSI接口连接一个小型摄像头定时拍照或录制延时视频记录植物生长过程。你可以使用picamera2库来操作。5.2 实现远程监控与控制虽然reTerminal有屏幕但你可能不在设备旁边。可以通过网络实现远程访问。轻量级Web服务器使用Flask或FastAPI创建一个简单的Web API服务器运行在reTerminal上。提供接口来获取当前传感器数据、历史图表图片以及手动控制水泵。from flask import Flask, jsonify, request app Flask(__name__) # ... 初始化传感器和逻辑对象 ... app.route(/api/data) def get_data(): moisture, temp, humi read_all_sensors() return jsonify({moisture: moisture, temperature: temp, humidity: humi}) app.route(/api/pump, methods[POST]) def control_pump(): state request.json.get(state) # ... 控制水泵 ... return jsonify({success: True})然后在同一网络下的手机或电脑浏览器访问http://reTerminal的IP:5000即可。集成物联网平台将数据上报到ThingsBoard、Home Assistant或各大云平台的物联网套件实现更强大的数据分析和跨设备联动。5.3 优化电源管理reTerminal可以通过GPIO控制电源通断。你可以添加一个数字光照传感器结合时间判断在夜间完全关闭屏幕背光以省电。甚至可以为整个系统设计一个基于太阳能板和电池的离线供电方案实现真正的“野外”部署。5.4 改进用户交互reTerminal板载了多个物理按键和一颗用户可编程的LED。你可以利用它们将某个按键设置为“手动浇水”按钮。将LED设置为状态指示灯例如慢闪表示运行正常快闪表示土壤缺水告警。在触摸屏上增加简单的按钮控件用于手动模式切换、阈值调整等。6. 项目总结与个人体会折腾完这个“MiniFarm on reTerminal”项目最大的感触是物联网应用的乐趣就在于这种从物理世界感知、到数字世界处理、再反馈回物理世界的完整循环。reTerminal作为一个高度集成、开箱即用的边缘设备极大地加速了这个过程让你能把精力更多地花在应用逻辑和交互设计上而不是纠缠于底层的硬件兼容性。几个让我印象深刻的点一是校准的重要性尤其是土壤湿度传感器不校准的读数几乎没有参考价值二是电源隔离的必要性驱动电机等负载一定要与核心控制板分开供电三是系统服务化带来的便利systemd让这个小项目有了“产品”般的稳定性和自恢复能力。这个项目就像一个种子你可以根据自己的兴趣随意嫁接新的功能。比如我后来就加了一个小风扇由温度传感器控制在温度过高时给植物降温还写了个脚本每天定时将传感器数据追加到CSV文件方便后期分析。它不仅仅是一个自动浇花装置更是一个可编程的、多功能的边缘计算实验平台。下次我或许会尝试在上面跑一个轻量级的机器学习模型根据图像识别来判断植物是否生病——这听起来又是一个全新的挑战。
返回列表