1. 项目背景与整体设计思路
接这个项目之前,客户那边的生产车间里已经有十六台焊接机器人和四台搬运机器人,专门做汽车挡泥板的生产。挡泥板这东西听着不起眼,却是汽车底盘防护的关键件,产线上的机器人每天三班倒,大概要产出两千件成品。问题不在于设备本身,而在于设备的管理方式——台账靠纸质记录、维修靠电话通知、产量靠人工统计、质量数据散落在各台工控机里。老板想搞一套管理系统,把设备状态、生产工单、质检数据、维修记录全部拉通,于是就有了这个基于Python的车辆挡泥板机器人工厂管理系统。
技术上选型很明确:主框架用Django,边路用Flask,都是Python生态里非常成熟的Web框架。Django负责整个业务管理后台,包括设备台账、工单管理、排产计划、质量追溯、报表统计这些重型业务。Flask则跑在现场的工控机和边缘网关盒子上面,专门做机器人运行数据的采集、状态上报和接口转发,它足够轻、足够灵活,装在一台四核小主机上也不占什么资源。
这套系统能解决的实际问题,简单归纳就是三件事:设备状态看得见、生产进度说得清、质量异常查得准。对车间主任来说,打开电脑就能看到每台机器人在干什么、当前工单完成到多少件;对维修工来说,机器人一报故障,系统自动生成维修工单并通知到人;对质量员来说,扫描一个产品二维码,就能追溯到是哪台设备、哪个班次、什么工艺参数下生产的。
这套系统比较适合两类人去参考:一类是工厂里做设备管理或信息化推进的工程师,想搞清楚数字化管理系统的实际落地路径;另一类是Python开发人员,尤其对Django和Flask组合架构感兴趣的同学。后面我会把整个系统的设计思路、核心模块、数据库建模、采集服务实现、以及上线过程中踩过的坑都拆开讲一遍,有些内容是用钱和时间换来的教训,希望帮大家少走弯路。
1.1 这条产线为什么需要一套管理系统
先说一个我调研时的场景:车间里有一台机器人的焊枪冷却水流量偏低,系统也报警了,但设备维护工没在跟前。这种报警会在机器人控制柜的触摸屏上闪烁,如果操作工刚好在产线另一头,就得等巡检人员走到跟前才能发现。传统的管理模式完全靠人巡、靠电话、靠交接班记录本,信息是断裂的。老板就算有心想抓设备综合效率,手里也拿不出靠谱的数据,因为整条产线的设备种类杂、品牌多,每台机器人都有自己的一套通讯接口和日志格式,人工汇总门牌号都记不住。
而挡泥板产线还有一个特点:机器人焊接节拍很快,单件加工时间非常短,一旦设备出问题停线,几十分钟就能积压一大片在制品。管理者需要实时掌握产线的运行状态,及时调度补产。这套管理系统就是把这些分散的信息统一收口,先解决数据有没有的问题,再解决数据准不准的问题,最后才是数据怎么用的问题。
1.2 技术选型:为什么是Django + Flask,而不是二选一
很多人会问:都是Python Web框架,为什么不用一套Django干到底,或者干脆全用Flask?这个问题的答案其实取决于系统各部分的运行环境差异。Django的优势在于全家桶——ORM、Admin后台、用户认证、权限管理、表单处理、自动化迁移,这些能力对于一个后台管理系统来说全是刚需,能省掉大量重复造轮子的时间。但Django也有它不太合适的地方:它的运行依赖比较重,部署一套完整环境要安装的第三方包多,启动内存占用高,对于现场工控机这种资源紧凑的环境来说并不理想。
Flask则恰恰相反,它核心只有一个路由分发和WSGI处理,本身不绑定数据库、不做权限体系、不强制定任何项目结构,你想怎么组织都行。把Flask跑在现场边缘网关里,配合Gunicorn或者简单的进程守护,一个采集服务从开发到部署可以控制在非常小的体积内。而且Flask写REST接口拿手,机器人厂商给的SDK和协议调用逻辑封装成独立的采集微服务,接口简单,出了问题也好排查,不影响主系统的稳定性。
另外还有一点很实际:这套系统的采购方可能是分批付款的,Django后台属于管理端,通常先上线验收;采集端属于现场数据基础设施,要跟着设备改造逐步推进。两个框架解耦之后,开发计划和验收节奏可以各走各的,不会互相牵制。如果未来某个环节需要换成FastAPI或者Go的采集服务,也不会波及到Django主系统的代码。
1.3 系统整体架构与模块划分
从部署结构上说,这套系统分三层。最上层是Django管理后台,部署在公司内网的一台服务器上,数据库采用MySQL,缓存用Redis,主要面向车间管理、工艺、质检、设备维修这些角色。中间层是Flask采集服务集群,分别部署在各条产线的边缘网关或者工控机上,向下对接每台机器人的控制器和PLC,向上通过HTTP API把数据写入Django的数据库或Redis队列。
最底层就是产线设备层,包括焊接机器人、搬运机器人、PLC、传感器和质检工位。整体架构并不复杂,核心是理清数据流向:设备状态数据从底层往上走,管理指令从上层往下走,两边在Django主系统的业务逻辑里交汇。
模块划分上,Django侧包括设备档案管理、工单排产管理、生产报工管理、质检追溯管理、维修保养管理、系统权限与操作日志。Flask侧包括设备心跳上报、实时状态转发、报警事件采集、机器人程序版本记录和参数回读。两边通过一套简单的鉴权Token进行通信,避免管理后台直接暴露在现场设备网络里。
2. 核心业务模块与数据库建模详解
系统做得好不好,一半看业务逻辑设计,一半看数据库建模。历史上很多工厂管理系统最后沦为"僵尸系统",原因往往不是功能不够,而是业务流程没有梳理清楚,数据库里存了一堆没法支撑业务的字段,界面做完了才发现设计跑偏。所以这个项目的数据库建模阶段,花的时间比写代码时间更长,我挨个车间跟了三个班次,才把每个业务角色的真实工作流程摸清楚。
2.1 设备管理模块:机器人台账与生命周期状态
设备是产线的核心资产,建模的时候要把一台机器人从进场到报废的整个生命周期管起来。我设计的模型里,机器人设备表是最基础的一张表,除了设备编号、名称、型号这类基本属性之外,还重点加了几个对工厂管理特别有用的字段:所属产线、安装位置、投用日期、运行状态、最后心跳时间。
设备表的状态字段不搞模糊概念,只定义四个值——online(在线运行)、offline(离线)、fault(故障)、maintain(保养/维修中)。这个状态不是人工在后台改的,而是由Flask采集服务根据机器人心跳和报警信号自动更新的,人工只做确认和收尾操作。设备保养记录单独建表,关联设备ID、保养类型、保养人员、保养日期和下次保养日期,后台可以设定提醒规则,接近保养周期就自动在首页看板弹提醒。
还有一个容易被忽略的关键字段是设备IP和通讯协议类型。不同品牌的机器人通讯方式不一样,有的支持OPC UA,有的只开放Modbus TCP,有的厂家提供HTTP接口的SDK。设备表里存下这些参数,Flask采集服务启动时才能根据设备类型自动选择合适的通讯插件。
2.2 工单与排产逻辑:生产进度的可视化管理
工单是整个生产流程的主线,从下达生产指令到最终完工入库,每一步状态都要有据可查。工单表的核心字段包括:工单号、挡泥板型号、计划数量、已完工数量、计划开始/结束时间、优先级、状态。工单状态机我设计成五个阶段:待排产(pending)、已下发(released)、生产中(in_progress)、已完工(completed)、已取消(cancelled),每个状态之间的流转在Django里用独立函数控制,不做自由跳转,避免数据被误操作搞乱。
排产逻辑分配给车间计划员在Django后台完成。计划员按交付日期倒排生产计划,选择对应产线,系统自动校验设备当前状态和生产日历,防止把工单排到正在维修的设备上。车间班组长在自己的平板电脑上能看到当班工单列表,点击"开始生产"后,工单状态自动切到生产中,同时系统给对应的机器人下发生产配方信息。生产过程中每完成一件合格品,质检工位扫描条码自动触发报工,已完工数量实时累加。
2.3 质量追溯:挡泥板检测数据如何关联到设备和批次
挡泥板的质量追溯在焊接工艺中尤其重要。焊道偏移、焊缝气孔、飞溅过多,这些问题如果不在当天发现,后续返工成本很高。质量模块我做了两级追溯:批次级和单件级。每批挡泥板对应一个批次号,批次号关联工单号、设备号、操作班组、工艺参数版本和原料批号。质检员抽检时扫批次号录入抽检结果,不良品数量直接扣减工单合格数量。
单件追溯则靠二维码实现。每件挡泥板从产线下来时打码,码中包含生产日期、班次、工位号、工单号序列。质量异常时扫这个码,就能查到当时焊接机器人的电流、电压、送丝速度、气体流量这些关键参数的最近记录。这些参数数据量很大,如果全部实时写MySQL压力太大,所以设计成定时汇总:详细参数保存到独立的汇总表,按工单维度归档,页面展示时按需查询。
2.4 核心数据表结构与Django模型实现示例
下面我把设备表和工单表的Django模型代码贴出来,这是实际项目中精简后的版本,保留了核心逻辑。模型定义遵循几个原则:所有业务表都有created_at和updated_at;需要做追溯的字段都设置db_index;状态字段用短字符串常量而不是数字,直接提高代码可读性。
from django.db import models class Device(models.Model): STATUS_CHOICES = ( ('online', '在线运行'), ('offline', '离线'), ('fault', '故障'), ('maintain', '维护中'), ) code = models.CharField(max_length=32, unique=True, verbose_name='设备编号') name = models.CharField(max_length=64, verbose_name='设备名称') device_type = models.CharField(max_length=32, verbose_name='设备类型') line = models.CharField(max_length=32, verbose_name='所属产线') position = models.CharField(max_length=64, verbose_name='安装位置') ip_address = models.GenericIPAddressField(verbose_name='设备IP') protocol = models.CharField(max_length=16, default='modbus', verbose_name='通讯协议') status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='offline', verbose_name='运行状态') install_date = models.DateField(null=True, blank=True, verbose_name='投用日期') last_heartbeat = models.DateTimeField(null=True, blank=True, verbose_name='最后心跳时间') remark = models.TextField(blank=True, verbose_name='备注') class Meta: db_table = 'device' verbose_name = '设备档案' indexes = [ models.Index(fields=['status', 'line']), ] def __str__(self): return f'{self.code}-{self.name}'class WorkOrder(models.Model): STATUS_CHOICES = ( ('pending', '待排产'), ('released', '已下发'), ('in_progress', '生产中'), ('completed', '已完工'), ('cancelled', '已取消'), ) order_no = models.CharField(max_length=64, unique=True, verbose_name='工单号') product_model = models.CharField(max_length=64, verbose_name='挡泥板型号') plan_qty = models.IntegerField(verbose_name='计划数量') completed_qty = models.IntegerField(default=0, verbose_name='合格完工数量') defect_qty = models.IntegerField(default=0, verbose_name='不良品数量') priority = models.IntegerField(default=0, verbose_name='优先级') plan_start = models.DateTimeField(verbose_name='计划开始时间') plan_end = models.DateTimeField(verbose_name='计划结束时间') actual_start = models.DateTimeField(null=True, blank=True, verbose_name='实际开始时间') actual_end = models.DateTimeField(null=True, blank=True, verbose_name='实际结束时间') status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='pending', verbose_name='状态') device = models.ForeignKey(Device, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='生产设备') created_by = models.ForeignKey('auth.User', on_delete=models.PROTECT, verbose_name='创建人') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') class Meta: db_table = 'work_order' verbose_name = '生产工单' indexes = [ models.Index(fields=['status', 'plan_start']), ] def __str__(self): return self.order_no提示:设备名称建议包含品牌和型号,比如"FANUC R-2000iC/210F",这样维修工看到设备列表就能直接判断配件型号,不用再去翻档案柜。
字段设计上有几个小心思:工单关联设备的ForeignKey用了SET_NULL而不是CASCADE,因为工单是生产追溯的重要凭证,即使设备从系统里删除,工单记录也必须保留设备信息和历史关联。模块间尽量不通过跨表外键互相拉扯,而是用查询条件关联,方便后续业务扩展时不容易被表结构卡住。
3. Django主系统的业务实现与关键代码
Django部分我的组织方式是按业务域建App,一个App管一组聚合的业务,这样不会出现一个App里堆几百个文件的情况。这个项目拆了四个App:accounts(用户与权限)、devices(设备与维修)、production(工单与排产)、quality(质检与追溯)。
3.1 项目管理结构与App创建
创建App这个动作本身没什么好说的,一条命令的事情,但App的边界划分是真正考验经验的地方。我见过一些项目把所有模型都放在一个App里,结果model.py文件超过两千行,谁都不敢动。这个项目的划分思路是:设备域里只放设备本身、通讯参数、维修保养记录,不掺生产计划;生产域里只放工单、排产、报工;质检域只管检验记录和质量分析。跨域的数据联动不靠外键强关联,而是通过业务服务层调用,代码里保持低耦合。
python manage.py startapp devices python manage.py startapp production python manage.py startapp quality python manage.py startapp accountsApp创建之后再逐个编写模型、视图、序列化和URL路由。Django的URL分发机制天然适合把各业务域的API分开管理,每一个App里建一个urls.py,主urls.py再统一include,接口路径一目了然,比如/devices/api/、/production/api/。
3.2 权限管理与登录认证的落地
工厂管理系统的角色权限和普通互联网项目不一样,操作人员是固定的内部员工,角色类型也相对稳定,不需要复杂的社交登录和验证码体系。我用Django内置的User模型配合Group做角色控制,细分了五类角色:系统管理员、车间计划员、班组长、质检员、维修工。每种角色在Django Admin里勾选对应的Group权限,视图层面再用@permission_required装饰器做接口防护。
登录认证方面,后台管理页面保持传统的Session认证,因为使用者是内部员工,浏览器操作方便最重要。Flask采集服务则不走Session,采用Token认证方式,在Django里生成一个长期有效的API Token下发给边缘网关,Flask请求时在Header里带上这个Token,Django侧用一个中间件校验。这样做的好处是,采集服务和主系统之间的通讯不依赖登录状态,即使现场工控机长时间没有交互也不用担心会话过期。
# Django中间件示例:校验Flask采集端的API Token from django.http import JsonResponse from django.conf import settings class ApiTokenMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if request.path.startswith('/api/collect/'): token = request.headers.get('X-Api-Token') if token != settings.COLLECT_API_TOKEN: return JsonResponse({'code': 401, 'msg': 'invalid token'}, status=401) return self.get_response(request)部署时把COLLECT_API_TOKEN配置在环境变量里,不要写死在settings.py,更不要提交到代码仓库。这个习惯我从一开始就坚持,后面换服务器或者多人协作开发时省了很多麻烦。
3.3 Django查询优化:避免报表页面卡死的经验
系统上线后最容易被吐槽的就是报表页打不开。挡泥板产线每天产生几万条报工和检测记录,如果查询写得粗糙,统计页面三秒都转不完。这里给两个最实用的优化经验。
第一,关联查询务必使用select_related和prefetch_related,避免N+1查询。之前我排查过一个设备列表页面卡顿的问题,页面显示五十台设备,每台设备要查最近一条维修记录,结果执行的SQL数量是五十加一次,数据库压力全浪费在这种低效查询上了。用了Prefetch之后,一次查询把五十条维修记录全部带出来,页面响应时间从1200毫秒降到80毫秒。
第二,报表统计别在Python里循环算,用聚合函数一次性算完。统计每个设备当月的产量,放在Django ORM里就是一条annotate加Count/Sum的聚合语句,让数据库把结果算好返回,而不是在Python里写循环然后一条条执行SQL。这个习惯在数据量小的时候看不出区别,数据量上来之后性能差距是数量级的。
4. Flask采集服务与机器人数据对接
如果说Django部分是管理系统的大脑,那Flask采集服务就是神经末梢。它直接面对产线上各种品牌的机器人,承受的是恶劣的现场环境、复杂的协议差异和不稳定的网络,这部分写不好,整个系统的数据基础就是空中楼阁。
4.1 机器人数据采集的几种常见方式
工业机器人对外通讯一般有几种方式:底层协议如Modbus TCP、OPC UA,厂商私有协议如FANUC的FOCAS、KUKA的RobotSensorInterface,还有部分新设备提供RESTful HTTP接口。这个项目里的机器人品牌比较杂,焊接机器人有FANUC和ABB,搬运机器人有KUKA,一时间没法统一协议。最后的方案是做了一个采集适配层,每一种协议封装成一个独立的采集类,对外暴露统一接口,上层调度逻辑不需要关心设备具体是哪个品牌。
Modbus TCP协议适合读PLC里中转的机器人状态字,比如运行/停止/报警/自动模式这些开关量。FANUC机器人可以通过FOCAS接口读取当前坐标、伺服电流、程序名这些生产数据。OPC UA则是未来的统一方向,新采购的设备基本都会支持。我建议工厂在规划设备采购时,尽量要求供应商开放OPC UA接口,后续做数据采集可以省去非常多的适配工作。
4.2 Flask采集服务的核心流程
采集服务的核心逻辑可以概括为三件事:定时轮询、状态判断、数据上报。工控机上部署的Flask服务启动时读取设备清单,为每台设备启动一个采集线程,采集线程按照设定的周期(一般是三秒到五秒)向机器人控制器发起通讯请求,拿到数据后立即解析成统一的JSON格式,然后通过HTTP POST上报到Django的采集接口。
心跳上报不同于普通数据上报,它专门用来判断设备和采集服务是否还活着。Flask服务每三十秒向Django发送一次心跳包,Django收到后更新设备表的last_heartbeat字段。管理后台看到设备状态变成"离线"时,先查last_heartbeat离当前时间多久,就能快速区分是设备本身断电了还是通信链路断了。
# Flask采集服务设备状态上报示例 from flask import Flask, jsonify, request import httpx import threading import time app = Flask(__name__) COLLECT_API_URL = 'http://server.internal:8000/api/collect/device_status/' API_TOKEN = 'your-token-here' def collect_and_report(device_info): while True: try: status = read_robot_status(device_info) payload = { 'device_code': device_info['code'], 'status': status['status'], 'program': status.get('program', ''), 'ctime': time.time() } resp = httpx.post( COLLECT_API_URL, json=payload, headers={'X-Api-Token': API_TOKEN}, timeout=5 ) except Exception as e: log_error(device_info['code'], str(e)) time.sleep(device_info.get('interval', 5))注意这里每一台设备一个线程的做法仅适用于设备数量几十台的规模,如果未来设备量扩大到几百台,需要引入异步IO协程方案,否则线程数会耗尽工控机资源。我在这个项目里踩了这个坑的雏形,现场一台四核工控机跑二十台设备线程,CPU占用率稳定在百分之六十左右,勉强能用,但规模扩大之前必须重构。
4.3 采集库与业务库的数据同步策略
Flask采集服务采集到的数据如果每一条都直接写MySQL,数据库写入压力会非常大,而且现场工控机的网络并不一定稳定,数据库连接断掉时数据就会丢。这个项目采用了两级缓存的策略:Flask服务本地先把采集结果写到SQLite数据库里作为缓冲,同时尝试推送到Django主系统的Redis队列里;Django后台有一个定时任务,每隔一段时间从Redis队列批量把数据写入MySQL,写入成功的消息才从队列里移除。
这套策略的好处是扛得住网络抖动。有一次客户现场检修网络,交换机连续重启了好几次,采集服务本地SQLite里存了将近两个小时的数据,网络恢复后Redis队列一次性补推成功,主库没有任何数据缺失。如果用直写方案,这种情况大概率会丢数据或者生产一堆重试异常。数据同步的补偿机制是整个系统稳定性的重要保障,这一点投入的代码量看似多了,但换来的是少接半夜的故障电话。
5. 典型业务场景实操流程
写后台管理系统,不能只看功能清单,要看实际业务流程跑得顺不顺。这块我挑三个最典型的场景完整走一遍流程,包括操作人员是谁、界面点了什么、系统后台发生了什么,方便读者参考映射到自己的项目里。
5.1 场景一:从创建工单到机器人上线的完整流程
车间计划员登录Django后台,进入生产管理-工单管理页面,点击新建工单。填写挡泥板型号、计划数量、计划开始与结束时间,选择优先级后保存。此时工单状态为待排产。计划员在排产列表里选中这张工单,系统自动列出当前空闲且状态为在线的设备,指定其中一台焊接机器人,点击下发,工单状态变为已下发。
班组长在车间的平板电脑上刷新工单列表,看到当班任务后点击开始生产。系统前端调用后端接口,后端把工单状态修改为生产中,记录实际开始时间,同时生成一个生产批次号。机器人操作工从设备触摸屏上加载对应程序,开始生产。
生产过程中,质检工位每完成一件扫描一次,系统调用报工接口,当前工单的completed_qty自动加一。当completed_qty达到plan_qty时,系统自动把工单状态改为已完工,记录实际结束时间,并在看板上亮起完成信号。
5.2 场景二:机器人报警与维修工单的处理闭环
焊接机器人向Flask采集服务上报状态为故障,Flask把报警事件推送到Django。Django后台收到报警事件后,先从设备表里查出这台设备对应的维修负责人,然后自动生成一条维修工单,同时向维修工手机端推送通知。维修工在手机上看到报警内容,到现场排查处理后,在系统里填写故障原因、处理措施和配件更换记录,提交关闭维修工单。
这个闭环里最值得注意的设计是报警等级分三级:低级别报警只记录日志,不生成工单;中级别报警生成工单但不通知;高级别报警既生成工单又实时推送。如果所有报警都推送,维修工一天要被骚扰十几次,系统很快就会被弃用。报警阈值和等级规则在后台配置化,每个设备类型可以单独设置。
5.3 场景三:质量异常时的追溯查询
质检员在生产过程中发现一批挡泥板焊缝气孔率异常,这时候需要在质量模块里发起追溯。在追溯查询页面输入批次号或者扫描单件二维码,系统返回该批次关联的工单信息、生产设备、操作班组、工艺参数版本,以及这台机器人在这段时间内的关键参数曲线。在参数数据基础上,质检员可以结合焊接电流、送丝速度是否偏移来判断是参数设定问题还是设备机械问题。
这个场景对系统性能有一点要求,因为参数曲线查询背后可能是几千条实时采样数据,前端展示需要做缩略处理。我的做法是后端在返回给前端之前先做降采样,把几千个点压缩成几百个展示点,既保证了趋势可见,又不会把浏览器撑爆。数据采样保留时间设定为十二个月,超过保留期的自动归档到历史表,控制主表体量。
6. 常见问题与排查技巧实录
系统从开发到上线,再到稳定运行,中间遇到的坑比想象中多得多。我把最有代表性的问题和排查思路整理成了表格,这些内容也是整个项目最值钱的经验所在。
6.1 高频问题速查表
| 现象 | 可能原因 | 排查思路与解决方法 |
|---|---|---|
| 报表统计页面响应缓慢 | 关联查询造成N+1,循环内执行SQL | 用select_related/prefetch_related优化;改聚合查询 |
| 设备状态长时间显示离线 | 采集线程崩溃或网络中断 | 检查Flask服务日志;看设备心跳时间;重启采集服务并确认守护进程自动拉起 |
| 机器人数据断档半小时 | 边缘网络抖动,Redis队列堆积 | 检查本地SQLite缓冲区数据;网络恢复后观察补推是否完成 |
| 工单状态卡在生产中不更新 | 报工接口异常或前端未同步刷新 | 查看报工接口日志;检查工单completed_qty是否达到plan_qty |
| 两台框架服务混在一起时session冲突 | Django和Flask共用同一个浏览器session | 明确服务边界,Flask服务不走Session,改用API Token |
| 部署后页面报500错误 | ALLOWED_HOSTS未配置或SECRET_KEY未设置 | 检查Django settings环境变量;查看DEBUG模式和错误日志 |
| 现场工控机与服务器时间不一致 | 时钟偏移未同步 | 配置NTP定期同步;采集数据统一用UTC时间戳存储 |
6.2 现场排查实录:一次诡异的设备离线问题
上线第二周,客户反馈车间里有一台ABB机器人在管理后台一直显示离线,但在机器人控制柜触摸屏上看设备运行完全正常。我远程登录Flask采集服务所在工控机,先看采集进程还在不在,结果进程还活着。接着看采集日志,发现机器人地址连通性测试一直超时。在工控机上直接ping机器人控制柜IP,通的;再用Modbus工具测试端口,却连不上。
最后定位到问题根源:机器人的通讯模块在长时间运行后进入了一种半死机状态,以太网口在物理层是通的,但协议栈不响应。把机器人控制柜的通讯模块断电重启后,采集恢复正常。这类问题靠写代码解决不了,必须让现场运维在巡检表里加上一条:每星期重启一次通讯模块。遇到类似情况,先别急着怀疑自己的代码,把链路里每一段的物理状态都排查一遍,很多"系统问题"其实是设备硬件问题。
还有一个容易忽略的坑:Django的时区设置。采集服务上报时传的是时间戳,Django的settings.py里TIME_ZONE设置为'UTC',USE_TZ设置为True,这样所有设备上报的时间在数据库里统一以UTC存储,展示时再转换为本地时间。如果一边用UTC一边用本地时间混着来,追溯时间线会乱得一塌糊涂。这类问题不会当场暴露,往往在事后分析数据时才致命,务必从一开始就统一口径。
6.3 部署过程中的依赖与版本管理经验
这个项目同时在服务器和现场工控机上跑,两个环境的Python版本和系统环境差异很大,依赖管理做不好就是灾难。我强烈建议用conda或者虚拟环境把Python运行时隔离起来,不要直接用系统自带的Python环境装包。Django项目建议锁定Python 3.10左右的版本,Django 4.2 LTS版本长期维护,稳定性好,适合企业项目。Flask采集服务则用更轻的依赖组合,能少装就少装,现场工控机常常没有外网,离线安装依赖包这件事非常折磨人。
我踩过一个典型的坑:现场一台工控机是精简版Linux系统,缺少一堆底层编译依赖,pip install第三方包时经常编译失败。后来我把所有依赖包提前下载成whl文件,连同部署脚本一起打包,拷贝到现场离线安装,才彻底解决了这个问题。做工厂项目,永远假设现场网络环境是不可靠的,依赖包、安装介质、部署文档这三样东西必须离线可用。
7. 系统后续扩展方向与个人收尾体会
这套系统上线试运行了一个多月,月底盘点时客户发现最大的变化是数据不用再对账了——产量数据和质检数据在系统里自然闭环,设备故障的平均响应时间从原来的两个多小时压缩到四十分钟以内。老板甚至主动要求加一个车间大屏展示页面,把产线状态和当日产量投到车间墙上,这其实是看得见的效果倒逼出来的需求。
从扩展角度讲,这套Django + Flask的架构向微服务演进也比较顺畅。如果后续要接ERP或者MES,可以在Django侧直接开发接口,或者把采集服务独立成更专业的边缘计算服务,增加边缘端的实时质量判断能力。如果要对接可视化大屏,Django提供数据API,前端用任意图表框架都行,我建议优先考虑集成国内成熟的大屏组件,开发者可以少花很多时间在图表适配和浏览器兼容性上。
从个人实际经验角度说几句。我体会最深的一点是:工业软件开发,七成功夫在弄清楚现场业务,三成功夫在写代码。你写的每一张表、每一个状态字段,背后都对应着车间里一个具体的人和他每天的工作习惯。多蹲几天产线,比多看几篇技术文档管用得多。技术选型上,Django和Flask的组合不是什么高大上的方案,但胜在分工清楚、各取所长、容易维护,对于中小型工厂的管理系统来说是非常务实的搭配。
最后分享一个小建议:整个部署过程中,一定要把Django的SECRET_KEY、数据库密码、采集服务的API Token这些敏感信息全部放在环境变量或者单独的配置文件中,任何情况下都不要提交到代码仓库。我在项目交付后做代码审查时,发现过不止一次私钥泄露的情况,严重的话可以直接通过Django的SECRET_KEY伪造会话登录后台。这个习惯越早养成越好,它不花什么成本,但关键时刻能帮你保住整个系统。