资讯动态

基于Python Flask的水产养殖水质监控与设备自动控制系统设计

发布时间:2026/10/21 21:20:25 来源:尧图企业网站定制
写这套系统的时候我脑子里一直有一个画面凌晨三点手机连着响了七八条告警推送养殖户从床上弹起来往塘边跑结果是传感器被水草缠住误报。后来把告警逻辑加上“连续两次确认”和“静默期”之后这种情况基本消灭了。这个“基于Python和Flask智能水产养殖管理系统”的项目就是围绕“让养殖户少跑腿、让数据替人盯塘”做的。标题尾部那一串“_1z11jbg7”是源码平台自动生成的编号不作用途。整个系统说白了就是一套水质监控设备自动控制的Web平台鱼塘里装溶解氧传感器、pH传感器、温度传感器数据通过串口或Modbus传到后台Flask负责提供接口和页面前端用图表把水质趋势画出来超过阈值就告警同时自动控制增氧机、投饵机这些设备。这套东西适合三类人一是要交课程设计的学生二是想给自己的小养殖场做自动化改造的养殖户三是想快速上手Flask完整项目开发的Web后端开发者。我写这篇复盘默认你已经会Python基础语法知道Flask怎么起一个最简单的服务。这里重点讲清楚一个多传感器、多鱼塘、带自动控制的管理系统是怎么从零搭起来、跑起来、并在实际环境中不崩的。1. 系统整体设计与架构选型思路1.1 为什么是Python和Flask而不是Spring Boot或Django先回答最常被问的问题为什么选Flask水产养殖管理系统的数据链路核心是“采集—传输—存储—展示—控制”。这五个环节里采集和传输是最前端的而Python在硬件通信生态上优势很大。读取养殖现场常见的溶解氧传感器、pH传感器基本都要走RS485串口或Modbus RTU协议Python的pyserial和minimalmodbus这类库可以直接搞定写起来非常顺手几分钟就能把传感器数据读到内存里。再说Flask。这种量级的系统用户量就是几个养殖场老板和操作员并发压力基本可以忽略。Flask胜在轻量和灵活单文件能起服务SQLAlchemy管数据库APScheduler管定时采集任务前端接口用Flask-RESTful或者直接写路由都行。你不需要像Django那样被固定的项目结构绑着手脚也不需要像Spring Boot那样维护一堆JVM层面的依赖。对中小型水产养殖场景来说杀鸡用不上牛刀。有人可能会问那生产环境稳定性够不够我的回答是这套系统真正跑起来之后Flask反而成了最不用操心的那一环出问题的地方基本都在硬件通信和数据库连接层面。只要部署时用gunicorn接管配好nginx反向代理Flask完全扛得住。1.2 技术栈与分层架构四层结构各管各的事整个系统我做了非常明确的分层这是后面调试不迷路的关键。你如果不分层所有代码堆在几个路由里一旦传感器掉线、数据库连接超时、定时任务重复执行同时出现排查起来会非常痛苦。感知层溶解氧传感器、pH传感器、水温传感器、液位传感器。这些设备通过RS485总线挂到采集终端上。传输层采集终端通过串口读取传感器数据然后通过以太网或4G模块把数据发送到服务器。简单方案里采集终端本身就是一台跑着Python脚本的小主机。应用层Flask应用负责数据接收、存储、展示、告警判断、设备控制指令下发。执行层继电器模块、增氧机、投饵机、水泵。应用层通过串口或继电器板卡发出开关信号控制强电设备。这个架构可以用一个特别朴素的类比来理解你给鱼塘请了个二十四小时不睡觉的值班员。传感器是值班员的眼睛传输层是眼睛看到信息后传递给大脑的神经Flask应用是大脑继电器和增氧机是手脚。大脑判断“溶氧太低”之后不是喊你起来开增氧机而是直接伸手去开。这套系统全部的意义就是把“人半夜起来巡塘”变成“系统自动响应”。1.3 功能模块的规划从需求到模块的拆解我在设计功能模块时没有直接按页面来分而是按“一个养殖场日常运营要关心什么”来分。一个养殖户每天早上看什么看溶氧、看温度、看氨氮有没有超标。每天要操作什么开增氧机、投饵、换水。隔一段时间要确认什么设备是不是坏了、数据记录是不是完整。对应到系统里就是这几个模块用户与权限管理不同角色看不同页面老板看总览技术员可以控制设备操作员只能看数据。鱼塘管理管理多个鱼塘的基础档案每个塘独立配置传感器和设备。实时数据监控当前所有水质指标更新频率可以配到每30秒。历史数据与图表按小时、天、周查看趋势曲线支持导出。告警管理阈值设置、告警通知、告警记录查询。设备控制手动控制和自动控制两套模式所有控制行为写日志。数据流也理得很清楚采集任务定时把数据写入数据库Web服务读库展示给用户告警模块在写入时同步判断控制模块根据最新数据决定设备状态。整个过程没有多余的数据来回拷贝。1.4 这套系统解决的核心痛点水产养殖的痛点不在代码在于“水下的事情看不见”。一个大问题溶解氧夜间下降速度比白天快得多凌晨四五点最容易缺氧浮头。如果没有监测设备等肉眼看到鱼浮头基本就晚了。另一个痛点是能耗浪费。增氧机开一晚上如果溶氧其实一直很高那就是白白交电费。系统可以根据实时溶氧自动开关增氧机既保命又省钱。我见过实际部署的塘口一个夏天电费能省两成左右前提是控制逻辑调得稳不能频繁启停把机器烧了。这套系统解决的就是“看得见”和“省着花”这两个问题前者靠传感监测和告警后者靠联动控制和趋势分析。这是整个项目的价值锚点。2. 数据库设计与核心功能模块实现2.1 表结构设计六张核心表别贪多数据库我用的是MySQL 8.0开发环境也可以用SQLite跑通逻辑部署再切MySQL。表结构是一个系统的骨架这块没设计好后面会很痛苦。这套系统我最终保留了六张核心表字段不追求多够用就行。用户表user字段id、username、password_hash、role、real_name、phone、create_time。角色分管理员和技术员、操作员三级。password_hash必须用哈希加盐不能存明文。鱼塘表pond字段id、pond_name、area、depth、fish_species、fish_count、create_time。每口塘是独立的自循环单元后面所有数据都带pond_id。传感器表sensor字段id、pond_id、sensor_type、sensor_code、unit、status、last_online_time。sensor_type我存的是枚举值temperature、ph、dissolved_oxygen、ammonia。一个塘可以挂多路同类型传感器比如两个溶解氧探头互为备份。水质数据表water_quality这是数据量最大的一张表。字段id、pond_id、sensor_id、temperature、ph、dissolved_oxygen、ammonia、collect_time。数据表按天做分区或者定期归档。这块非常重要采集频率如果设成5分钟一次一张表一天就有288条记录十口塘就是2880条一个月就是86000多条。如果不做清理查询会越来越慢。我给采集频率设成每5分钟一轮历史数据保留三个月超过的自动归档到另一张冷表。设备表device字段id、pond_id、device_name、device_typeaerator是增氧机、feeder是投饵机、pump是水泵、status、switch_modeauto或manual、last_control_time。设备状态和控制模式是分开的因为“设备在线”和“允许自动控制”是两回事。控制日志表control_log字段id、pond_id、device_id、actionon/off、trigger_typemanual是手动、auto是自动、alarm是告警联动、trigger_detail、create_time。这张表非常关键它记录了每一次设备的开关动作后期排查“为什么增氧机半夜自己开了”全靠它。2.2 用户登录与权限控制别自己造轮子也别太复杂用户认证这块我建议不要用Flask-Login那套老方案直接自己用Session加Token做都行。核心就两个点密码哈希用werkzeug.security里自带的generate_password_hash登录后把用户信息写进Session然后每个需要权限的接口用装饰器校验。角色权限我做了三层管理员可以配置系统参数和管理用户技术员可以控制设备和调整告警阈值操作员只能看监控数据。每个路由函数上标注admin_required、tech_required判断逻辑就一句当前用户角色是否在允许名单里。这里有个容易被忽略的点控制设备的接口必须单独校验不能和查看数据的接口共用一套权限逻辑。我之前就见过一个系统普通操作员能通过改请求参数把别人的增氧机关了。权限最小化原则在这种场景下不是一句空话。2.3 水质采集与告警判断定时任务加多级确认采集功能是整个系统的地基。硬件层面传感器通过RS485总线连接电脑或树莓派上用pyserial读取Modbus RTU协议发送查询帧。以溶解氧传感器为例读到的原始值需要根据温度做补偿换算这一步可以在采集端做也可以交给服务端做。软件层面我用APScheduler的BackgroundScheduler跑定时任务from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger import time def collect_all_ponds(): # 读取所有鱼塘配置 ponds get_all_ponds() for pond in ponds: try: data read_sensor_by_serial(pond[serial_port], pond[sensor_modbus_addr]) save_water_quality(pond[id], data) check_alarm(pond[id], data) except Exception as e: log_warn(f采集失败: {e}) scheduler BackgroundScheduler() scheduler.add_job(collect_all_ponds, IntervalTrigger(minutes5), idcollect_job) scheduler.start()告警判断我做了两层设计。第一层是阈值判断温度超过35度、pH低于6.5或高于8.5、溶氧低于3.0mg/L、氨氮高于0.2mg/L就进入告警流程。第二层是连续确认不取一次数据就判定而是连续三次采集也就是15分钟内都超阈值才真正推送告警。这个设计非常重要因为传感器在高温高湿的养殖现场会不定期出现毛刺数据可能某一次读出来溶氧突然掉到0.5下一次又恢复4.5。如果见一次就告警你的手机一晚上能响几十次第二天就免疫了真正的险情反而没人看。告警通知在开发环境用控制台输出就行真实环境可以用极简方案企业微信群机器人或者短信接口。核心逻辑是一样的先把告警记录写库再推送通知通知失败不能影响告警记录落库。2.4 设备自动控制状态机思路解决“频繁启停”问题设备控制最容易翻车的地方不是“开不了机”而是“开机之后立刻又关机”一晚上把继电器开关几十次增氧机马达早晚烧掉。所以我控制逻辑里加了一个最小运行时间和最小停机时间的概念。增氧机的控制我用了一个简化状态机def control_aerator(pond_id): latest get_latest_quality(pond_id) device get_device(pond_id, aerator) now time.time() # 强制手动模式优先 if device.switch_mode manual: return # 确认运行/停机状态 if device.status on: # 溶氧低于阈值下限且开始回升继续运行 if latest.dissolved_oxygen 5.0 and (now - device.last_switch_time) 600: turn_off(pond_id, auto) elif device.status off: if latest.dissolved_oxygen 3.5 and (now - device.last_switch_time) 600: turn_on(pond_id, auto)两个关键参数溶氧高于5.0并且持续时间超过10分钟才停机溶氧低于3.5并且持续时间超过10分钟才启动。3.5到5.0这个区间是“死区”设备保持当前状态不变。这个死区设计是整套控制系统最核心的经验你抄走直接能用。3. 前端可视化与API接口设计3.1 页面结构一个大屏、一套后台、一个H5页面我分了三个端监控大屏、管理后台、H5手机端。监控大屏适合养殖场办公室挂一个大电视看总览管理后台用来做设备控制和数据查询H5方便老板在外面出差时瞄一眼关键指标。监控大屏的布局其实很固定顶部是当前日期时间和整体告警摘要中部是各大鱼塘的关键指标卡片下方是两组曲线图左边是溶氧趋势右边是温度趋势。每个鱼塘一个卡片卡片里显示实时值和状态色块绿色正常、黄色预警、红色告警。管理后台里鱼塘列表可以直接切换选中一个塘之后右侧显示该塘的传感器实时数据、设备状态、控制按钮和最近告警记录。整个页面不用做太花哨信息层次清楚最重要。H5端我只做了最核心的三件事看实时指标、看告警消息、远程启停增氧机。手机上操作频率最高的就是这三件其他功能没必要搬到手机上。3.2 实时图表轮询比WebSocket更省心前端图表库我选的是ECharts它自带完整的折线图、仪表盘、K线图能力在养殖场景下够用且好看。实时刷新方案很多人一上来就想用WebSocket其实在这种场景下轮询精度完全够了实现起来也更稳。我用了最简单的轮询方案前端每30秒请求一次最新水质接口拿到数据后更新卡片数值并把新数据点追加到图表里。后端把最近24小时的数据按时间切分前端只保留当前屏幕能显示的约120个点数据窗口向前滑动。核心的API接口习惯上按REST风格定义比如app.route(/api/pond/int:pond_id/latest, methods[GET]) def api_pond_latest(pond_id): data get_pond_latest(pond_id) if not data: return jsonify({code: 404, msg: 暂无数据}), 404 return jsonify({ code: 0, data: { pond_id: pond_id, temperature: data[temperature], ph: data[ph], dissolved_oxygen: data[dissolved_oxygen], ammonia: data[ammonia], time: data[collect_time] } })轮询的缺点你自己心里有数就行数据刷新有延迟、服务器压力比WebSocket大。但在这个系统量级下30秒一次全站也就几百个请求一点压力没有换来的是部署时省掉一层Nginx代理WebSocket的配置复杂度。我认为这笔账很划算。3.3 接口安全与异常处理给接口加上防御措施接口这部分安全细节很容易被人忽略我踩过坑所以单独拿出来说。一是接口不能裸奔。至少用Token做一层访问控制前端登录后把Token存本地每次请求放在请求头里。Token生成用Redis存也可以没Redis就用签名方式自校验简单有效。二是所有接口必须有统一异常处理。Flask里用app.errorhandler全局捕获异常返回JSON而不返回HTML否则前端拿到的是个看不清的错误页。采集数据接口尤其要注意传感器离线时返回statusoffline数据字段返回null前端要兼容这种情况该显示“传感器离线”就显示不能白屏。三是控制类接口要做幂等和防误触。连续点击两次“开增氧机”第二次请求如果设备已经是开的状态就直接返回成功但不产生重复动作。前端用按钮loading状态也能防但后端必须自己兜底。4. 智能决策逻辑与多场景适配4.1 从“超阈值告警”到“趋势预判”多走一步的智能标题里有“智能”两个字但说实话真正算法级别的智能在水产养殖场景里用得有限目前阶段最有价值的是“基于趋势的预判”。最简单的实现就是“线性外推”。采集模块写入数据库时每个鱼塘保存最近1小时的数据点观测量变化速率。比如当前溶氧是4.0mg/L并且最近15分钟持续下降按这个速率算30分钟后会跌破3.0的危险阈值。系统可以提前一小时推送“预计凌晨3点溶氧跌破阈值建议提前开启增氧机”这样的提醒。这在代码里其实就是移动平均加一阶拟合def predict_do(history_data, minutes30): if len(history_data) 3: return None # 取最近30分钟计算平均变化速率 recent history_data[-6:] deltas [recent[i1][value] - recent[i][value] for i in range(len(recent)-1)] avg_speed sum(deltas) / len(deltas) predict_value recent[-1][value] avg_speed * (minutes / 5) return predict_value这个预判逻辑虽然粗糙但在实际场景里非常有用。晚上十点看到系统推送“预计凌晨2点溶氧偏低”你就可以把增氧机定时提前开起来不用等告警响了你才醒。4.2 喂养决策参考从“凭经验”到“有依据”投饵量是养殖户每天最关心的问题之一。传统做法是老养殖户根据天气、水温、鱼的吃食情况凭经验估。系统能做的是给一个参考区间减少拍脑袋的成分。参考公式很简单按鱼体重的百分比计算日投饵量 塘内鱼总重 × 投饵率其中投饵率由水温决定水温低于15度时1%以下18到22度时1%到2%25到30度时2%到3%。溶氧太低时自动调低投饵率因为低氧环境下鱼消化能力会下降投多了反而污染水质。系统不是直接控制投饵机而是在管理后台给出一个“建议投饵量”的卡片让技术员确认后一键下发。全自动投饵在现阶段反而不可靠因为鱼的摄食状态很难仅靠水质参数判断。4.3 多鱼塘扩展配置数据与业务数据彻底分离系统从一塘扩展到十塘难度不在于加几张表而在于从一开始就坚持“鱼塘不做死编码”。所有查询、采集、控制逻辑都必须带pond_id没有pond_id的表要么是字典表要么就是设计错了。一个鱼塘的设备配置、传感器配置、告警阈值都是独立的。在管理后台里复制一口塘的配置到另一口塘是常用的功能因为很多塘的情况是类似的。传感器离线处理也要区分塘粒度。一口塘传感器全部离线大概率是串口断连单独一个传感器离线大概率是探头坏了。这两类异常在监控大屏上要显示不一样的标记方便判断是通信问题还是设备问题。5. 部署上线与运行维护5.1 开发环境与依赖管理起步阶段少踩坑项目依赖我建议直接用requirements.txt锁版本Python版本固定用3.10。整个依赖清单其实就这些核心库flask、flask-sqlalchemy、pymysql、pandas、apscheduler、pyserial、minimalmodbus、requests。虚拟环境一定得建别把Python全局环境搞乱。一个典型的新手问题是用系统自带的pip装了一堆包版本冲突之后整个环境报废。用virtualenv或者python -m venv都行至少把项目环境隔离起来。采集硬件在开发阶段可以先用模拟器代替。写一个fake_sensor.py每5秒生成一组随机但符合实际范围的数据这样后端开发和前端页面完全不需要依赖真实硬件。真实传感器到了之后再替换数据源即可接口不用变。5.2 生产环境部署gunicorn加nginx加supervisor三板斧开发环境里直接运行python app.py是没问题生产环境必须换方案。Flask自带的Werkzeug开发服务器性能太差而且不稳定生产环境我用的部署组合是gunicorn nginx supervisor全部在一个Linux服务器上跑。gunicorn的启动命令就这么一行gunicorn -w 2 -b 127.0.0.1:8000 wsgi:app两个worker就够了因为系统日常工作主要是定时采集、数据库读写和API响应CPU密集型操作几乎没有。supervisor负责守护进程进程掉了自动拉起[program:aquaculture] command/opt/aquaculture/venv/bin/gunicorn -w 2 -b 127.0.0.1:8000 wsgi:app directory/opt/aquaculture autostarttrue autorestarttrue stderr_logfile/var/log/aquaculture_err.log stdout_logfile/var/log/aquaculture_out.lognginx配置核心的location块server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /opt/aquaculture/static/; expires 7d; } }这里有个细节采集任务如果跑在Web进程内部gunicorn开两个worker可能导致定时任务重复执行。我的处理方案是把采集任务单独抽成一个独立的采集进程不跑在Web进程里。用supervisor同时管理两个进程一个叫aquaculture-web一个叫aquaculture-collector各自独立互不干扰。这是部署这套系统时最值得记住的一个设计决策。5.3 数据备份与服务器运维出故障时最感激自己的两条规则数据是这套系统唯一不能丢的东西。我设置了两个备份任务数据库每天凌晨全量备份到本地目录然后同步到另一台存储机器同时开启binlog确保万不得已时可以恢复到任意时间点。日志也要重点管起来。Flask的请求日志、采集任务的日志、设备控制的日志全部写到独立文件并且按天切分。排查问题时第一件事永远是看日志文件而不是去猜。养殖场现场往往是三四十度的高温高湿环境服务器可能就放在配电房里散热差导致死机的情况也遇到过远程告警和自动重启脚本能省很多来回跑腿的时间。6. 常见问题与排查技巧实录6.1 高频问题速查表下面这些问题是我在实际运行过程中真实遇到过的不是埋着头写代码时能想象到的。做成速查表放在这里你遇到类似情况直接查现象根因解决方式数据采集偶尔丢点串口被其他进程占用采集进程和Web进程分离不共用串口告警消息凌晨轰炸传感器毛刺数据触发一次判定连续三次超阈值才告警加入静默期增氧机频繁启停控制逻辑缺少死区设置最小运行/停机时间死区内状态不变定时任务重复执行gunicorn多个worker同时启动任务采集任务独立进程不用Web进程调度查询历史数据越来越慢水质表数据量膨胀按天分区三个月以上数据归档传感器一直读到NaN探头浸在水中但接线松动或探头损坏重启采集终端用备用探头替换确认页面打开很慢前端一次请求加载全部历史数据后端分页或按时间窗口返回前端只保留120个点数据库连接报错SQLAlchemy连接池耗尽调大pool_size和max_overflow关闭长时间空闲会话6.2 最值得说的一段排坑经历有一次整个系统的数据显示全部延迟了半小时采集任务日志却显示一切都正常。排查了很久才发现问题不在代码在数据库。水质数据表的insert当时走了SQLAlchemy的ORM批量会话每批数据提交前都要查询一次设备状态结果设备状态接口写的慢查询把数据库的线程池占满了形成“采集线程等数据库数据库等资源释放”的死循环。这个问题的教训就是采集任务的写入路径上绝对不能有重量级查询。我把写入逻辑改成先直接拼SQL批量insert不经过ORM的会话提交后再单独异步查询设备状态。修改之后整个数据链路恢复顺畅。凡是和生产链路相关的代码越直接越好越多一层封装就多一分潜在问题。6.3 传感器校准和硬件联调的实操心法传感器是这套系统里最容易出“看起来能用”问题的部分。溶解氧探头出厂时通常有校准参数但实际使用一段时间后会有漂移。投入现场使用前至少要做一次饱和校准和零点校准校准后标定一个基准值存到系统配置里。系统在换算和展示时加上这个基准值的修正。还有一点很容易被忽略同一个鱼塘里不同位置的溶氧差异极大。靠近增氧机的地方溶氧高池塘底部角落溶氧低。传感器探头装在哪里采集到的数据就代表哪里。如果目标是判断“全塘是否缺氧”探头应该装在远离增氧机、水深中下层的位置那才是溶氧最容易拉胯的地方。这个位置选错系统再稳都白搭。硬件联调时还有一个对时间敏感的问题采集终端的系统时间必须同步。曾经出现过采集终端时间快了五分钟导致水质数据的时间戳比真实时间靠前前端趋势图看起来像“未来的数据”。后来在脚本里加了系统时间和服务器时间对时这个问题才彻底消掉。最后说点实在的这套系统从立项到最终稳定运行给我最大的体会是技术实现本身并不难难的是“采集要稳、判断要准、控制要果断”这三件事同时做好。代码写出来的那一刻项目其实才刚起了个头真正的工作量在真实场景里调参和排障。做类似系统的朋友我建议先花半天时间把传感器探头放水里测几组真实数据再用这些数据去配阈值比闭门造车写一万行代码都有用得多。如果你在学校里拿这个当课题做尤其注意老师看的不只是功能完成度而是你对异常情况的处理能力。把传感器掉线、数据库抖动、设备误动作这些场景都处理干净展示出来的系统才真的有说服力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑