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

资讯详情

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

多块大屏同步显示:工厂车间可视化电子看板从采集到展示的工程实践

多块大屏同步显示:工厂车间可视化电子看板从采集到展示的工程实践

1. 项目缘起与整体设计思路

1.1 为什么工厂车间需要可视化电子看板

在制造现场待过的人都有一个共同感受:数据不是没有,而是散落在各个角落。MES系统里有工单进度,PLC里有设备状态,质检那边有合格率,仓库那边有库存水位,但这些数据各自躺在不同的屏幕、不同的系统、甚至不同人的Excel表格里。车间主任想看一眼当前产线的实时产出,得打电话问班组长;厂长想了解当天各线体达成率,得等统计员下午出报表。这种信息滞后,在快节奏的生产环境里就是实打实的效率损耗。

可视化电子看板要解决的核心问题就一个:把分散的、滞后的、需要人工汇总的数据,变成实时的、集中的、一眼能看懂的大屏画面。上海这边很多制造企业——尤其是汽车零部件、电子组装、精密加工这类行业——车间面积大、产线多、设备品牌杂,对看板的需求尤其强烈。一块好的电子看板,能让管理者在车间走一圈就掌握全局,能让操作工抬头就知道当前任务和进度,能让异常在发生的头几分钟就被暴露出来。

这个项目标题里提到的“多块大屏同步数据显示”,是实际落地中最容易被低估的难点。单块屏幕显示数据不难,难的是让分布在车间不同位置、不同楼层、甚至不同厂区的多块大屏,在同一时刻显示完全一致的内容,并且当数据源更新时,所有屏幕几乎同时刷新。这背后涉及数据采集、传输协议、时间同步、渲染策略等一系列工程问题,不是随便找个前端页面投屏就能糊弄过去的。

1.2 整体架构选型:为什么这么搭

先把我最终落地的架构摆出来,再解释每一层为什么这么选。

数据流向大致是:设备层(PLC、传感器、扫码枪)→ 采集层(边缘网关、OPC UA客户端)→ 数据处理层(MES接口服务、消息队列)→ 数据分发层(WebSocket服务、NTP时间同步)→ 展示层(多块大屏终端)。

采集层我选的是边缘网关加OPC UA的组合。车间里的设备品牌很杂,三菱、西门子、欧姆龙、台达都有,协议从Modbus到Profinet到EtherNet/IP不一而足。边缘网关的好处是它把协议转换这件事下沉到了设备侧,网关统一用OPC UA或MQTT往上送数据,上层服务不用关心底下是什么牌子的PLC。上海这边有些老厂区网络条件一般,边缘网关还能做本地缓存,网络抖动时数据不丢,恢复后补传。

数据处理层直接对接MES系统的数据库和API。这里有个坑要提前说:很多MES厂商的数据库表结构是加密的或者文档缺失,直接读库风险很大。我的做法是优先走MES提供的WebService或REST接口,实在没有接口的字段,再考虑只读方式查库,并且一定要加只读账号和查询限流。热词里提到的“webservice mes”和“mes系统开源”其实反映了两种路线——用商业MES的走接口,用开源MES的可以直接改代码加数据出口,各有各的玩法。

数据分发层是整个同步显示的关键。我用的方案是:后端服务订阅消息队列(RabbitMQ或Kafka),收到数据变更后通过WebSocket推送给所有大屏终端。为什么不用HTTP轮询?因为轮询的实时性和服务器压力是一对矛盾——轮询间隔短了服务器扛不住,间隔长了数据滞后。WebSocket是长连接,服务端有数据就推,延迟可以做到毫秒级,而且多块屏幕收到的是同一条消息,天然同步。

时间同步这块单独拎出来说。多块大屏如果各自用自己的本地时间做数据时间戳,哪怕差几秒,显示出来的“最后更新时间”就会不一致,操作工看到会懵。NTP在这里的作用就是让所有终端、服务器、采集网关的时间基准统一。华为云NTP服务器地址是很多国内项目的首选,延迟低、稳定性好。我在项目里配置的是内网自建NTP服务加华为云NTP做备用,终端每5分钟同步一次,偏差控制在50毫秒以内。

1.3 多块大屏同步的核心难点与对策

同步显示这件事,拆开来看有三个层面:数据同步、渲染同步、视觉同步。

数据同步靠WebSocket广播加消息序号机制。每条数据变更消息带一个自增序号,终端收到后按序号处理,乱序到达的消息会被暂存等待。这样即使网络有轻微抖动,最终所有屏幕处理的数据顺序是一致的。

渲染同步靠统一的渲染时钟。我在前端用requestAnimationFrame配合一个全局的帧计数器,所有屏幕的动画和刷新都对齐到这个时钟上。听起来有点过度设计,但实际跑下来,当大屏上有滚动字幕或闪烁报警时,没有这个机制,几块屏幕的动画节奏会肉眼可见地不同步。

视觉同步最容易被忽略的是屏幕本身的差异。不同品牌、不同批次的LED屏,色温和亮度曲线不一样,同一张画面投上去颜色会有偏差。我的处理是在每块屏幕的播放端加一层色彩校正配置,用同一张标准色卡照片做参照,逐块调整伽马值和白平衡。这个活很琐碎,但做完之后多屏一致性提升非常明显。

2. 核心细节解析与实操要点

2.1 数据采集:从PLC到看板的完整链路

先讲最底层的采集。车间里一台典型的加工设备,我需要从它身上拿到几个关键数据:运行状态(运行/停机/故障)、当前工单号、已完成数量、节拍时间。这些数据通常在PLC的寄存器里,地址需要跟设备厂商或电气工程师确认。

以三菱FX系列为例,假设运行状态在D100寄存器,0表示停机、1表示运行、2表示故障。边缘网关的配置大概是这样:

# 网关采集点配置示例 points: - name: "machine_status" protocol: "melsec" address: "D100" data_type: "uint16" scale: 1 offset: 0 deadband: 0 # 状态变化立即上报 - name: "completed_count" protocol: "melsec" address: "D200" data_type: "uint32" scale: 1 offset: 0 deadband: 1 # 数量变化1才上报

这里有个实操细节:deadband(死区)的设置很关键。像完成数量这种累计值,如果每变化1就上报一次,高频生产时消息量会爆炸。我一般设死区为1,同时加一个最大上报间隔(比如5秒),保证即使数量不变也能定期刷新心跳。状态类数据死区设0,因为状态变化必须立即反映。

采集频率方面,状态数据我设的是200毫秒扫描一次,数量数据500毫秒。再快没有意义,因为人眼看大屏的刷新感知也就到几百毫秒级别,而且会给PLC通讯增加不必要的负担。实测下来,一台网关带30台设备,200毫秒周期,CPU占用不到15%。

2.2 MES数据对接:接口优先,查库兜底

MES系统是看板数据的重要来源,工单信息、计划数量、实际产出、合格率这些通常都在MES里。对接MES我踩过的坑最多,这里展开说。

第一选择是走MES官方接口。商业MES一般会提供WebService或RESTful接口,虽然文档可能不全,但至少是官方支持的,升级后不容易挂。调用时注意加超时和重试,MES服务器在出报表时响应会变慢,看板服务不能被拖死。

# MES接口调用示例(带超时和重试) import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503]) session.mount('http://', HTTPAdapter(max_retries=retry)) def fetch_work_order(order_no): try: resp = session.get( f"http://mes-server/api/order/{order_no}", timeout=(3, 10) # 连接3秒,读取10秒 ) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: # 记录日志,返回缓存数据或None logger.error(f"MES接口调用失败: {e}") return None

第二选择是只读查库。有些老MES没有对外接口,只能读数据库。这时候务必做到:用单独的只读账号、只查必要的表和字段、加查询超时、避免全表扫描。我见过有人直接SELECT * FROM production_order把MES库拖垮的,这种事千万别干。

第三选择是让MES主动推。如果MES支持消息通知或触发器,可以在关键表上建触发器,数据变更时写入一张中间表,看板服务轮询中间表。这种方式对MES侵入小,实时性也不错。

热词里提到的“skywalking能部署到mes制造系统上面吗”,其实反映的是可观测性需求。我的建议是看板服务自己做好日志和指标监控就行,MES系统本身要不要上APM,得看MES厂商的支持态度,强行部署可能影响MES稳定性。

2.3 多屏同步的通信设计

WebSocket服务我用的是Node.js加ws库,或者Python的FastAPI加websockets,两者都跑过,性能都够用。关键设计点有三个:

连接管理:每块大屏终端建立连接时带上自己的标识(如screen-01、screen-02),服务端维护一个连接池。终端断线重连时,服务端要能识别并恢复推送。

消息广播:数据变更时,服务端向所有连接广播同一条消息。消息体包含数据内容、时间戳、序号。

// WebSocket广播示例 const clients = new Set(); wss.on('connection', (ws, req) => { const screenId = new URL(req.url, 'http://localhost').searchParams.get('id'); ws.screenId = screenId; clients.add(ws); ws.on('close', () => clients.delete(ws)); }); function broadcast(data) { const message = JSON.stringify({ seq: ++globalSeq, ts: Date.now(), payload: data }); for (const client of clients) { if (client.readyState === WebSocket.OPEN) { client.send(message); } } }

心跳与重连:终端每30秒发一次ping,服务端回pong。超过90秒没收到ping就认为连接已断,清理连接池。终端侧检测到连接断开后,用指数退避策略重连(1秒、2秒、4秒、8秒,最大30秒)。

注意:WebSocket连接数不是越多越好。如果大屏数量超过50块,建议引入Redis Pub/Sub做消息中转,WebSocket服务做成多实例,每个实例只管一部分连接,Redis负责跨实例广播。

2.4 NTP时间同步的配置细节

时间同步这件事,不出问题的时候感觉不到,一出问题就是大问题。我遇到过因为某块屏幕时间慢了3分钟,导致看板上“最后更新”时间显示异常,操作工以为数据卡死了,实际数据是新的。

NTP配置分三层:

服务器层:看板服务器和采集网关都配置NTP客户端,指向内网NTP服务或华为云NTP。Linux下用chrony,Windows下用系统自带的时间同步。

# chrony配置示例 /etc/chrony.conf server ntp.internal.company.com iburst server ntp.myhuaweicloud.com iburst driftfile /var/lib/chrony/drift makestep 1.0 3 rtcsync

makestep 1.0 3的意思是前3次同步如果偏差超过1秒就直接跳变,之后 gradual 调整。这个配置对看板服务器很重要,因为服务器时间跳变会导致消息时间戳混乱。

终端层:大屏播放终端(通常是迷你PC或工控机)也要配NTP。Windows终端可以用组策略统一推送NTP服务器地址,Linux终端用chrony或systemd-timesyncd。

应用层:即使系统时间同步了,应用层也要做校验。我在看板前端加了一个逻辑:收到消息后,对比消息时间戳和本地时间,如果偏差超过5秒,在屏幕角落显示一个时间异常提示。这个提示帮我们提前发现过好几次终端时间漂移的问题。

3. 实操过程与核心环节实现

3.1 从零搭建看板服务的完整步骤

假设你从一台干净的Ubuntu服务器开始,下面是我实际跑通的步骤。

第一步:基础环境准备。安装Docker和Docker Compose,后续所有服务都用容器跑,方便迁移和备份。

# 安装Docker curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # 安装Docker Compose sudo apt install docker-compose-plugin

第二步:部署消息队列。用RabbitMQ,管理界面方便排查问题。

# docker-compose.yml 片段 services: rabbitmq: image: rabbitmq:3.12-management ports: - "5672:5672" - "15672:15672" environment: RABBITMQ_DEFAULT_USER: kanban RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS} volumes: - rabbitmq_data:/var/lib/rabbitmq

第三步:部署数据采集服务。我用Python写了一个采集服务,通过OPC UA客户端读边缘网关的数据,处理后发到RabbitMQ。

# collector.py 核心逻辑 import asyncio from asyncua import Client import aio_pika import json async def main(): # 连接OPC UA网关 opc_client = Client("opc.tcp://gateway:4840") await opc_client.connect() # 连接RabbitMQ mq_conn = await aio_pika.connect_robust("amqp://kanban:pass@rabbitmq/") channel = await mq_conn.channel() exchange = await channel.declare_exchange("kanban_data", aio_pika.ExchangeType.FANOUT) # 订阅数据变化 nodes = { "status": "ns=2;s=Machine1.Status", "count": "ns=2;s=Machine1.CompletedCount" } while True: data = {} for key, node_id in nodes.items(): node = opc_client.get_node(node_id) data[key] = await node.read_value() await exchange.publish( aio_pika.Message(body=json.dumps(data).encode()), routing_key="" ) await asyncio.sleep(0.5) asyncio.run(main())

第四步:部署WebSocket推送服务。用FastAPI加websockets,订阅RabbitMQ,收到消息后广播给所有大屏。

# ws_server.py 核心逻辑 from fastapi import FastAPI, WebSocket, WebSocketDisconnect import aio_pika import json import asyncio app = FastAPI() clients = set() @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): await ws.accept() clients.add(ws) try: while True: await ws.receive_text() # 保持连接 except WebSocketDisconnect: clients.remove(ws) async def consume_mq(): conn = await aio_pika.connect_robust("amqp://kanban:pass@rabbitmq/") channel = await conn.channel() queue = await channel.declare_queue("kanban_broadcast", durable=True) async with queue.iterator() as it: async for message in it: async with message.process(): data = message.body.decode() for client in list(clients): try: await client.send_text(data) except Exception: clients.discard(client) @app.on_event("startup") async def startup(): asyncio.create_task(consume_mq())

第五步:大屏终端页面。前端用Vue或React都行,核心是WebSocket连接和数据渲染。

// 大屏前端核心逻辑 const ws = new WebSocket(`ws://kanban-server/ws?id=${screenId}`); let lastSeq = 0; const pendingMessages = new Map(); ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.seq === lastSeq + 1) { render(msg.payload); lastSeq = msg.seq; // 检查暂存区是否有后续消息 while (pendingMessages.has(lastSeq + 1)) { const next = pendingMessages.get(lastSeq + 1); render(next.payload); lastSeq = next.seq; pendingMessages.delete(lastSeq); } } else if (msg.seq > lastSeq + 1) { pendingMessages.set(msg.seq, msg); } }; // 断线重连 ws.onclose = () => { setTimeout(() => location.reload(), 3000); };

3.2 多块大屏的部署与调试

大屏终端的选型上,我推荐用迷你PC(如Intel NUC或国产工控机)而不是智能电视自带系统。原因有三:一是浏览器可控,能锁定版本和配置;二是可以远程管理,批量部署和更新;三是性能稳定,长时间运行不卡顿。

部署时每块屏幕的配置项包括:屏幕ID、WebSocket服务器地址、NTP服务器地址、色彩校正参数、显示布局模板。这些配置我统一放在一个JSON文件里,终端启动时读取。

{ "screenId": "line-a-01", "wsServer": "ws://10.0.1.100:8000/ws", "ntpServer": "ntp.internal.company.com", "colorProfile": "profile-a.json", "layout": "line-dashboard", "refreshInterval": 1000 }

调试多屏同步时,我用的方法是:在服务端加一个“测试模式”,每秒广播一个递增的计数器,所有屏幕同时显示这个计数器。如果肉眼看到某块屏幕的数字落后,就说明那块屏幕的网络或渲染有问题。这个方法很土但非常有效,能快速定位是网络延迟还是终端性能问题。

3.3 数据刷新策略与性能优化

看板数据不是刷新越快越好。我的经验是分三类处理:

状态类数据(运行/停机/故障):变化即推,无变化时每30秒推一次心跳。这类数据实时性要求最高。

计数类数据(完成数量、合格数):每5秒推一次,或者变化超过死区时立即推。这类数据变化频繁,全量推送会压垮前端。

统计类数据(达成率、OEE):每30秒或1分钟推一次。这类数据计算量大,在服务端算好再推。

前端渲染也要做优化。大屏上通常有表格、图表、滚动字幕等多种元素,如果每次数据更新都全量重绘,性能会很差。我的做法是用虚拟DOM做差异更新,只重绘变化的单元格。图表用ECharts的话,用setOption的增量更新模式,不要每次clear再setOption。

实操心得:大屏浏览器建议开启硬件加速,并且禁用不必要的插件和扩展。我遇到过因为浏览器某个扩展导致内存泄漏,大屏跑两天就卡死的情况。后来统一用Chrome的kiosk模式,干净很多。

4. 常见问题与排查技巧实录

4.1 数据不同步的排查思路

多块大屏不同步是最常见的问题,排查要按链路逐段来。

先看服务端:在WebSocket服务上加日志,记录每条广播消息的序号和发送时间。如果服务端日志显示消息是按序发出的,问题就在终端侧。

再看网络:在终端上用ping和traceroute检查到服务器的网络质量。如果延迟波动大或丢包,考虑换有线连接或调整网络拓扑。

最后看终端:在终端浏览器控制台看WebSocket消息的到达时间和序号。如果消息到达时间一致但渲染时间不同,就是终端性能问题,需要优化前端渲染或升级硬件。

下面这张表是我整理的问题速查表:

现象可能原因排查方法解决措施
某块屏数据滞后网络延迟或终端性能不足对比消息到达时间戳换有线网络,优化渲染
所有屏数据都滞后服务端处理慢或MQ积压看服务端日志和MQ队列长度优化查询,增加消费者
数据偶尔跳变消息乱序或重复检查消息序号加序号校验和去重
时间显示不一致NTP未同步对比各终端系统时间统一配置NTP
屏幕颜色不一致屏幕硬件差异用标准色卡对比逐块色彩校正

4.2 MES接口超时与数据缺失的处理

MES接口超时是家常便饭,尤其是月底出报表的时候。我的处理策略是:

缓存兜底:看板服务本地缓存最近一次成功获取的数据,接口超时时先用缓存数据顶着,同时在屏幕角落显示“数据可能延迟”的提示。

异步刷新:不要在看板请求路径上同步调MES接口。用后台任务定期拉取MES数据写入本地数据库,看板只读本地库。这样MES慢不会直接影响看板。

降级策略:如果MES接口连续失败超过阈值,自动切换到只显示设备直采数据,MES相关字段显示为“--”。等接口恢复后自动切回。

4.3 大屏长时间运行的稳定性问题

大屏是要7x24小时跑的,稳定性比功能更重要。我踩过的坑包括:浏览器内存泄漏、WebSocket连接假死、终端系统自动更新重启。

内存泄漏:前端代码里注意清理定时器和事件监听。我习惯在组件销毁时统一清理,用AbortController管理fetch请求。

连接假死:WebSocket的onclose不一定能捕获所有断线情况。我加了一个应用层心跳:终端每30秒发一个ping,服务端回pong,连续3次没收到pong就主动重连。

系统更新:终端工控机一定要关闭自动更新。Windows用组策略,Linux用unattended-upgrades的配置。我见过大屏在半夜自动更新重启,第二天早上车间主任发现屏幕是黑的。

避坑技巧:给每块大屏配一个智能插座,支持远程重启。遇到终端卡死又远程连不上的情况,直接断电重启,比派人去现场快得多。这个土办法救过我好几次。

4.4 LED屏体相关的注意事项

虽然这个项目主要是软件层面的同步显示,但LED屏体本身也有几个点要注意。

驱动芯片的消隐时间:如果大屏是LED拼接屏,驱动芯片的消隐时间设置不当会导致低灰度下出现鬼影。这个参数通常在屏体控制软件里调,不同品牌的芯片(如聚积、明微)默认值不同,需要根据实际显示效果微调。

刷新率与拍摄:如果车间有拍摄需求(比如远程巡检摄像头),LED屏的刷新率要设高一些,否则拍出来会有扫描线。一般建议1920Hz以上。

亮度自适应:车间光照变化大,白天和晚上的屏幕亮度需要自动调整。我用的是一个光照传感器加脚本,根据环境光强动态调屏幕亮度,既省电又护眼。

5. 项目落地后的效果与个人体会

这套看板在上海这边一个汽车零部件工厂落地后,最直观的变化是:车间主任不用再拿着对讲机问产量了,抬头看屏就行;异常停机从原来的平均15分钟才被发现,缩短到2分钟内报警;每天的生产报表从人工统计2小时,变成系统自动生成。

我个人在实际操作中的体会是,电子看板这个事,技术难度不在单点,而在系统工程。数据采集、传输、处理、展示,每一环都有坑,而且往往是环环相扣的。比如NTP没配好,会导致消息时间戳混乱,进而让同步逻辑出错,最后表现为屏幕数据不一致。排查的时候如果只盯着屏幕看,永远找不到根因。

另外一点是,不要追求大而全。我见过一些项目一开始就想把MES、ERP、WMS、QMS所有数据都堆到看板上,结果数据源太多、接口太杂,项目拖了半年上不了线。我的建议是分期做:第一期只做设备状态和产量,跑稳了;第二期加工单和合格率;第三期再加OEE和异常分析。每期上线后收集一线反馈,迭代优化。

最后分享一个小技巧:看板上一定要留一个“数据更新时间”的显示,精确到秒。这个小小的字段,在排查问题时价值巨大。操作工看到时间在跳,就知道系统是活的;工程师看到时间停了,就知道该查哪里了。

返回列表