我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

基于OpenClaw的水产养殖设备自动化测试与水质监测实践

基于OpenClaw的水产养殖设备自动化测试与水质监测实践 1. 项目背景与需求拆解1.1 这个项目是怎么来的做这个项目起因是我一个做室内水产养殖的朋友托我给他搞一套设备自动化测试和监测脚本。他养的是龙虾室内循环水养殖规模不大但有三个养殖池加一个暂养池设备却不少增氧泵、循环水泵、加热恒温棒、冷水机、紫外杀菌灯还有一堆传感器——水温、pH、溶解氧、氨氮。以前全靠人工早晚抄表再手动调设备结果出了两回事故一回是加热棒失控池水温度一路飙到32℃以上整池龙虾应激死亡过半另一回是增氧泵半夜停摆溶解氧跌到2mg/L以下损失也不小。问题根源很一致设备运转状态和关键水质指标没有自动化记录和告警等到人发现已经晚了。所以他提出需求时不是要一套纯记录数据的监测平台而是要一套“设备自动化测试”系统。这个说法很关键不只是被动采集水质数据还要主动去测设备本身是否正常工作。比如温控设备既要看当前水温是多少还要定时测试加热棒通电后能不能正常升温、冷水机在高温下能不能及时启动。这比单纯“数据上云”复杂一点但对养殖来说确实更有用。1.2 核心功能拆解整个需求拆成四块第一块水质指标监测。水温、pH、溶解氧、氨氮浓度这四样是龙虾池最核心的指标。不同指标、不同池子要有独立的阈值超限告警。第二块温控设备自测试。加热棒、冷水机、循环泵、增氧泵这些设备不能只看开关状态关键是要验证“给电之后设备真的在工作”比如加热棒启动后水温有没有变化、增氧泵启动后电流是否正常。第三块定时调度与联动。系统需要按照固定周期采集数据并且在异常时自动联动备用设备比如主加热棒故障时自动切到备用加热棒。第四块告警与留痕。所有监测数据、测试结果、告警记录需要保存下来便于事后排查。如果没有框架这种需求也可以直接写几百行Python加crontab解决但做到后面会很痛苦调度逻辑、设备驱动、数据存储、告警渠道全部耦合在一起加一个传感器、改一个阈值都要翻代码。后来我选用了OpenClaw这个轻量自动化测试编排框架用配置加少量脚本的方式把这套系统搭了起来一个树莓派大小的主机就跑得很稳。OpenClaw给我的第一印象很像一个“针对设备场景的精简版pytest加定时任务混合体”但它本身不绑定具体硬件而是通过YAML配置定义设备、任务、断言和动作跑在Python环境里特别适合这种单机、低资源、多设备联动的场景。2. 整体设计思路与方案选型2.1 为什么不上“大平台”做设备数据采集市面上有几种常规选择。一是Home Assistant这类智能家居平台支持很多种设备和自动化功能确实强大但对这种养殖场景来说太重了。它的核心是家庭自动化权限模型、前端面板、插件中心这些对我们用处不大而且部署在只有1GB内存的板子上跑起来明显吃力。二是Node-RED之类可视化流编排工具拖拽节点确实直观。但问题是我们这里不是做简单的开关联动而是要写测试逻辑、做数据校验、记录详细日志这属于“测试框架”的语义放在Node-RED的flow里会很别扭。三是纯自写Python脚本加crontab。这方案我一开始也考虑过毕竟硬件选型确认之后驱动和采集逻辑本身不算复杂。但做到后面会失去控制多池子的采集任务要错峰执行给温控设备做自测试需要构造一个完整的动作序列告警还要分级并支持ACK这些如果全在raw代码里实现维护成本会快速上涨。所以最后选了OpenClaw。它的定位就是“轻量级任务编排与设备测试框架”核心要素刚好覆盖需求YAML定义设备模型、支持cron调度、插件机制可以扩展Modbus/继电器/存储等驱动、自带断言器可以做阈值判断和测试用例结果记录。整个框架跑起来只有几个进程资源占用极低非常适合现场设备侧的嵌入式主机。2.2 OpenClaw的逻辑模型我搭建时把项目拆成几层设备层描述物理设备。每个设备有一个逻辑名字比如heater_pool_a、temp_sensor_pool_a对应真实的传感器或者继电器通道。设备信息包括通信方式、寄存器地址、单位、量程、数据换算公式。任务层描述“什么东西在什么时间做什么事”。比如每5分钟读取一次水质指标每小时做一次温控巡检每天凌晨3点做一次加热棒完整性测试。每个任务是一个可编排的执行流程包含若干步骤步骤之间可以引用上一步结果做断言。动作层描述发生了异常之后做什么。比如发送告警、切换备用设备、记录事件、本地蜂鸣器报警。存储层与报表层。SQLite保存所有历史数据每天自动汇总日报输出给用户查看。这套逻辑模型的优点是设备和任务解耦。新接入一个水质探头只需要在YAML文件里多加一个节点配置再在监测任务里引用一次不需要改代码。2.3 部署环境与硬件选型项目使用了某国产低成本ARM小板2GB内存只跑OpenClaw和其依赖的数据库平时内存占用稳定在300MB以下。现场供电有12V和220V两路传感器和控制器走12V加热棒和冷水机走220V。控制220V大功率设备必须用交流接触器中转板子上的继电器只驱动接触器线圈这样既能保证安全也让控制回路的可靠性高很多。传感器方面温度用的是PT100高精度变送器比DS18B20稳定量程0~100℃精度±0.1℃很适合水产养殖的温控需求。pH、溶解氧、氨氮分别接了三台工业变送器统一走RS485 Modbus RTU协议。选择统一为Modbus是一项很重要的决定一块USB转RS485模块就能把全部仪表串起来地址从1分配到4软件读取逻辑也统一不用为每种仪表单独写驱动。3. OpenClaw环境搭建与配置实战3.1 安装与目录规划OpenClaw依赖Python 3.9以上版本。把源码拷到板子后直接用pip安装依赖再安装框架本体即可。没有用容器因为设备侧对实时性和资源占用都敏感容器反而多余。项目目录我建议这样规划方便后续扩展openclaw_project/ openclaw.yaml # 框架全局配置 devices/ # 设备定义 pool_a.yaml pool_b.yaml common_sensors.yaml tasks/ # 任务与用例定义 monitor_water.yaml temp_control_test.yaml plugins/ # 扩展插件 modbus_reader.py relay_switch.py notify.py sqlite_store.py store/ # SQLite数据文件目录 reports/ # 每日生成的报告 logs/ # 运行日志框架启动时扫描devices和tasks目录把它们全部加载成内存中的设备树和任务调度表。这比把所有东西写在一个大配置文件里清晰很多调试时直接看对应文件就行。3.2 设备定义文件示例以一个养殖池为例设备定义如下id: pool_a name: A池设备组 devices: - name: temp_sensor type: modbus_rtu serial: /dev/ttyUSB0 slave_id: 1 register: 0 register_type: holding datatype: int16 scale: 0.1 unit: ℃ warning_range: [18, 30] error_range: [15, 32] - name: ph_sensor type: modbus_rtu serial: /dev/ttyUSB0 slave_id: 2 register: 0 register_type: holding datatype: int16 scale: 0.01 unit: pH warning_range: [6.0, 8.5] error_range: [5.5, 9.0] - name: dissolved_oxygen_sensor type: modbus_rtu serial: /dev/ttyUSB0 slave_id: 3 register: 0 register_type: holding datatype: int16 scale: 0.01 unit: mg/L warning_range: [4.0, 20.0] error_range: [3.0, 20.0] - name: ammonia_sensor type: modbus_rtu serial: /dev/ttyUSB0 slave_id: 4 register: 0 register_type: holding datatype: int16 scale: 0.01 unit: mg/L warning_range: [0, 1.0] error_range: [0, 2.0] - name: heater_relay type: relay port: gpio17 default_state: off - name: cooler_relay type: relay port: gpio27 default_state: off - name: heater_current_sensor type: modbus_rtu serial: /dev/ttyUSB0 slave_id: 5 register: 0 register_type: holding datatype: int16 scale: 0.1 unit: A注意温度精度和量程一定要仔细读仪表说明书。很多变送器内部寄存器用的是有符号整数小数位数通过scale字段还原如果datatype选错读出来会出现负数或者异常大数。3.3 调度任务配置示例水质监测任务配置如下id: monitor_water name: 水质定时监测 cron: */5 * * * * device_groups: [pool_a, pool_b] steps: - name: read_sensors plugin: device action: read_all on_success: store asserts: - sensor: temp_sensor type: in_range - name: check_alarm plugin: evaluator rules: - if: temp_sensor.value 30 then: notify_critical - if: dissolved_oxygen_sensor.value 3 then: notify_critical - if: ammonia_sensor.value 1 then: notify_warning这里的schema表达力比一般crontab强很多我在实际使用中最常用的就是asserts和rules两段。前者用来对采集值做即时校验后者用来组合多个指标触发分级告警。温控自测试任务配置就需要更多步骤了后面专门展开。3.4 框架的运行机制任务调度器使用cron表达式触发同一时刻允许最多2个任务并发执行默认顺序执行。执行过程中每个步骤的结果都会写进SQLite的task_run表包含执行时间、状态和输出摘要。如果某个步骤抛异常框架默认记录失败并继续执行后续步骤但可以将某一步标记为critical失败时直接终止当前任务并触发告警动作。这一点对设备测试场景很重要比如加热棒自测试步骤如果读不到电流数据说明硬件链路本身有问题就应该停止后续测试直接告警而不是继续执行。框架还支持一个很实用的特性叫“步骤依赖”。后面测试步骤引用了前面步骤的返回值就像写程序时的上下文传递却又不需要维护全局变量。例如读取水温之后把数值保存在上下文下一步断言判断这个值是否在前一步的合理范围内。4. 水质与温控监测脚本开发4.1 水质数据采集脚本设备定义完成之后真正干活的是各plugin模块。Modbus读取插件是我自己写的基于minimalmodbus库代码很短# plugins/modbus_reader.py import minimalmodbus SLAVE_MAP {} def read_sensor(sensor_config: dict) - dict: instrument_key (sensor_config[serial], sensor_config[slave_id]) if instrument_key not in SLAVE_MAP: instrument minimalmodbus.Instrument( sensor_config[serial], sensor_config[slave_id] ) instrument.serial.baudrate 9600 instrument.serial.bytesize 8 instrument.serial.parity N instrument.serial.stopbits 1 instrument.serial.timeout 0.8 SLAVE_MAP[instrument_key] instrument instrument SLAVE_MAP[instrument_key] raw instrument.read_register( sensor_config[register], sensor_config[scale].as_tuple()[1] if False else 1 ) value raw * sensor_config[scale] return {name: sensor_config[name], value: round(value, 3), unit: sensor_config[unit]}上面这段我做了简化实际项目里数据有效性判断才是重点。仪表偶尔会产生偶发坏值比如断电恢复、浪涌、总线冲突时读出来的寄存器值会瞬间跳到量程上下限。如果直接把坏值存库一方面曲线图很难看另外可能触发误告警。所以我对采集值做了两层清洗。第一层物理量程过滤超出传感器量程的值直接丢弃并记录一次sensor_error事件。第二层变化率过滤短时间变化幅度超过设定的最大变化率时判定为受干扰造成的异常跳变取前一次有效值替代。清洗逻辑在插件里实现不在任务定义中体现保证任务配置足够干净。4.2 温控逻辑与回差设计温控不只是“低温加热、高温制冷”这么简单。最容易被忽略的参数是回差hysteresis。如果加热启动温度设为26℃停止温度也设为26℃那么加热棒会在26℃附近反复短时间通断既伤设备又让水温波动大。实测下来加热棒每分钟通断三四次都可能出现继电器寿命骤减。实际配置如下A池目标温度区间为26℃到28℃。当水温降到26℃以下且低于设定下限1℃时开启加热棒。加热到28℃以上时关闭加热棒。冷水机在29.5℃时启动降到27.5℃时停止。加热棒和冷水机启动之间必须有至少2℃的缓冲区避免两者对冲。这个回差控制我放在温控任务中实现。任务每30秒采集一次温度然后根据当前状态机决定是否动作。id: temp_control name: 温控自动巡检 cron: */0.5 * * * * state: temp_control_state steps: - name: read_temp plugin: device action: read_single device: temp_sensor - name: update_state_machine plugin: python entry: state_machine.py任务定义看起来简单因为复杂的状态判断都封装在了python插件入口中。我这么做是为了让现场逻辑改动不用动调度框架只需改脚本。状态机用一段Python函数实现def control(state, temp, config): if state heating: if temp config.heating_off_temp: return off return heating if state cooling: if temp config.cooling_off_temp: return off return cooling # off if temp config.heating_on_temp: return heating if temp config.cooling_on_temp: return cooling return off这个状态机的价值在于它能避免控制输出抖动的潜在问题。即使任务崩溃重跑重启后读取上一次状态依然可以光滑地恢复控制。4.3 设备自测试脚本设备自测试是这套系统的灵魂。普通监测只是“看到水温高了才告警”自测试则是“加热棒该工作的时候证明它能工作”。每天凌晨3点在龙虾活动量最低的时候执行一次加热棒完整测试。测试步骤是先读取当前水温T_before。把温控状态机切到手动模式禁止自动温控干扰测试。打开主加热棒继电器等待60秒后读取水温T_after。读取加热棒电流值I_heater。断言T_after - T_before大于0.2℃I_heater大于3A且小于5A按设备功率计算。如果以上断言通过则说明加热棒正常恢复自动温控。如果失败切换备用加热棒再次执行该测试。备用通过则触发主加热棒故障告警并保持备用加热棒接管状态。断言的阈值不是拍脑袋定的。A池水体约2吨主加热棒功率是4kW理论上工作60秒能让水温上升约0.03度但考虑到循环水流带来的混合和热量散失实测正常状态下60秒能上升0.3到0.5℃。把判定阈值设为0.2℃留出冗余既不会漏报也不会因正常波动误报。电流传感器量程同样按设备功率校准过4kW加热棒在220V电压下电流约18A所以实际选用50A量程的电流互感器取0.1A精度。我在配置里用的当前返回的读数实际是基波电流值不是简单按功率除以电压但验证结果和钳形表对比误差在2%以内。4.4 告警与存储数据存到SQLite后为了让现场人员快速看到我额外写了两个产出。一是实时状态页直接读取数据库最新一条记录生成一个轻量级HTML页面放在局域网上手机浏览器直接打开即可查看。二是每日报告用pandas汇总每天四个池子的最高最低平均温度、溶氧、pH并列出所有告警事件。告警通知走两级warning级别只记录并在第二天日报中汇总critical级别立刻通过HTTP回调推送到消息网关再转到手机。为什么不在告警邮件里写细节因为现场人员半夜接个电话只需要知道哪个池子哪个指标超限细节可以早上看报告。5. 实测运行与故障诊断实例5.1 72小时连续运行数据系统上线后我记录了连续七十二小时的数据这里挑A池作为参考。水温整体维持在26.4到28.1℃之间没有出现大的波动。pH日变化比较明显白天因水草光合作用二氧化碳减少pH略微升高夜间回落范围在7.1到7.6之间。溶解氧波动更大白天水体光合作用产氧溶氧能到8mg/L以上凌晨最容易掉到4mg/L以下。整个过程中溶氧最低值出现在第三天凌晨3点21分回看当时的监测记录增氧泵运行电流偏小只有正常值的一半。推断是水泵气路被部分堵塞虽然设备还在转但实际增氧效率下降。传统的人工作业根本没法察觉这种“设备在转但工作没达标”的故障这正说明设备自测试的价值。5.2 温控设备真实故障复盘上线第11天凌晨4点40分告警声把朋友吵醒加热棒自测试失败。现场排查发现加热棒继电器触点烧蚀虽然继电器线圈吸合了但主触点没有完全导通加热棒处于半断连状态。如果靠自然水温池子会慢慢降温到早上才能被人工发现。这次故障在凌晨四点被识别出来自动切换到了备用加热棒。备用加热棒接管后的测试结果是正常升温水温在半小时内恢复了设定区间。事后检查烧蚀原因是因为安装时没有严格控制继电器到接触器之间的接线端子扭矩接触电阻偏大长期高电流运行导致端子发热碳化。这种问题在普通设备维护里特别容易忽略。5.3 数据准确性的验证刚开始运行时我对自动化监测的数据并不完全信任专门拿了便携式仪表在同样位置做了十组人工比对。温度比对偏差在0.1℃以内pH偏差在0.05以内溶氧偏差在0.3mg/L以内。说明传感器和变送器精度达标。但要注意探头长期泡在养殖水中会有结垢、漂移。pH探头每周至少校准一次溶解氧探头每两周清理一次传感器表面。这里我做了时间提醒任务每周三上午推送“校准提醒”避免现场人员忘记。养虾养鱼的水质自动监测数据准不准不全在设备校准体系一样重要。6. 常见问题与排查实录6.1 通信层问题汇总485总线上多台设备最容易遇到地址冲突。新设备出厂默认地址也是1直接并联上就可能导致两台设备同时应答主机侧读到乱码。处理方式是挨个设备单独连接用配置工具把slave_id改成规划好的编号。寄存器读取出错也很常见。不同品牌温控器有保持寄存器、输入寄存器和线圈寄存器之分同样的地址在不同寄存器类型下含义完全不同。插件里要明确指定register_type不能只看地址数字一样就套用相同逻辑。总线供电不足同样会导致读数批量漂移。我在A池加装第三台变送器之后总线上四个设备偶尔轮流读数异常原因就是线路太长压降大。后来把仪表供电线和485信号线分开走线单独拉一路12V电源给变送器问题消失。6.2 数据层问题汇总坏数据有两类一类是瞬时尖刺一类是阶梯漂移。尖刺靠变化率过滤基本能拦截干净。阶梯漂移一般发生在探头污染或者校准液污染后视觉上像是读数突然平移了一段距离过滤逻辑拦不住只能靠定期校准排查。遇到过一个特殊情况氨氮传感器在鱼缸中长时间工作后电极内部形成极化读数比真实值高很多。这一般出现在长期无人维护的场景里所以校准提醒任务不是可有可无的功能是必须做的基础设施。6.3 控制层问题与安全注意事项继电器控制220V设备不能把板子的GND和强电有任何直接连接。控制端一定要用光耦隔离继电器再驱动接触器。强电线槽和控制线槽尽量分开走前期省了一米线后期可能遭殃的就是整块板子的稳定性。加热棒长期在腐蚀性水环境附近工作金属外壳生锈、接线盒潮气重这些问题从视频上发现问题时经常已经非常危险了。实际运行中我保留了本地蜂鸣器因为现场的人不可能一直盯着手机现场物理声光告警比远程信息可靠得多。6.4 容易踩的坑速查表现象原因处理办法多个传感器读数间歇性错误485地址冲突或总线供电不足单独排除地址冲突独立电源供电温度突然显示-999寄存器datatype设置错误检查int16/int32、有符号无符号温控继电器频繁吸合缺少回差控制设置至少1.5℃的回差区间加热棒通电但水温不升接触器触点烧蚀或继电器不导通用电流传感器做备份判断水质数据每天同一时间跳变隔壁大功率设备启动干扰错峰采集加抗干扰处理断电重启后历史数据丢失SQLite未开启wal模式openclaw.yaml里配置wal模式7. 这类项目还能怎么扩展如果你也打算做类似的设备自动化测试我建议你从一块板子、一个池子起步先把一个池子的监测、温控、自测试闭环跑通再加第二个池子。设备抽象做得清晰的前提下加池子只是加配置的事。数据积累一段时间之后可以做两个有价值的扩展。一个是设备寿命预测把继电器吸合次数、加热棒工作时长、冷水机启动次数统计出来在达到设计寿命阈值前提前提示换备件。另一个是水质趋势预警不只看当前值是否越限还分析pH连续下降的趋势提前几天提醒水草过度分解或者硝化系统活力不足。有一个小技巧值得单独说所有设备测试任务都尽量放在凌晨执行这个时段水质相对稳定设备负荷低环境干扰少测试结果可比性最强。白天执行容易受到投喂、换水、上下浮游生物等因素干扰明明设备正常断言却失败容易造成告警疲劳。这套系统到现在已经稳定跑了几个月中间只因为电源模块损坏断电一次重启后靠SQLite的WAL模式完整恢复数据没有丢失任何历史记录。整体维护成本比我预想的低很多。如果你也在搞水产养殖或者其他类似的设备自动化监测轻量框架加设备自测试的思路是很值得尝试的实践路线。
返回列表