
1. 从一块屏到一面墙上海工厂看板项目的真实起点去年秋天接了个活儿上海郊区一家做汽车水冷板的工厂车间里已经有三块55寸的电视屏挂在墙上分别显示注塑机状态、装配线产量和当日不良率。问题是这三块屏各跑各的数据来源不一样刷新节奏不一样有时候左边显示当班产量1200右边还停在1150上班组长交接班的时候得拿手机拍照对数据对不上就扯皮。老板的原话是我要的是站在车间任何一个角落抬头看到的数字是同一个数字。这句话听起来简单做起来涉及的东西比想象中多。多块大屏同步显示核心矛盾不在显示而在同步——数据从MES系统出来经过中间层处理再分发到不同物理位置的屏幕中间任何一个环节的时钟不一致、刷新策略不一致、缓存策略不一致都会导致肉眼可见的数据打架。这个项目最后落地的方案是MES作为唯一数据源中间加一层数据聚合服务所有屏幕通过统一的WebSocket通道接收推送时间基准统一走NTP校时刷新策略由服务端控制而非各屏自主决定。整套东西跑下来硬件成本不高三块屏加一台工控机加一个交换机但调试过程踩的坑足够写一篇长文。下面我把从需求拆解到最终验收的完整链路拆开讲重点放在为什么这样设计和实际调试中会遇到什么。提示本文涉及的MES对接、NTP校时、WebSocket推送、大屏适配等内容均基于通用工业场景的常见实践具体参数需根据现场网络环境和设备型号调整。2. 多屏同步的底层逻辑为什么各自刷新一定会出问题2.1 三块屏数据打架的根本原因很多人第一反应是让每块屏自己去请求数据不就行了这个思路在小规模场景下确实能跑但只要屏幕数量超过两块且数据源不是静态的就一定会出问题。原因有三层第一层是请求时序不一致。屏幕A在10:00:00.100发起请求屏幕B在10:00:00.350发起请求如果这250毫秒内MES里刚好有一条新数据写入两块屏拿到的就是不同版本的数据。在产量统计这种高频写入的场景下这种偏差几乎必然发生。第二层是本地缓存策略不一致。浏览器或前端框架通常会有自己的缓存机制有的屏可能因为网络抖动触发了重试拿到的是一分钟前的缓存数据而另一块屏正常拿到了最新数据。两块屏并排挂着数字差一截操作工会立刻质疑系统可靠性。第三层是时钟基准不一致。如果前端用本地时间做最后更新时间的显示而三块屏的系统时间没有统一校时就会出现数据一样但时间戳不一样的尴尬。工厂环境里设备长时间运行系统时钟漂移是常态不校时的话一天下来差几秒很正常。所以正确的做法是数据请求和分发由服务端统一控制屏幕只负责渲染。服务端拿到MES数据后通过长连接通道同时推给所有屏幕屏幕不做任何自主请求只被动接收。这样三块屏收到的数据包是同一份渲染出来的数字自然一致。2.2 推送通道选型WebSocket还是SSE在工业看板场景下实时推送有两个主流选择WebSocket和Server-Sent EventsSSE。我最终选了WebSocket理由如下对比维度WebSocketSSE通信方向全双工单工服务端到客户端浏览器兼容全支持除IE外全支持断线重连需自行实现浏览器自动重连数据格式二进制/文本仅文本适用场景需要客户端上报状态纯推送看板场景其实SSE就够用因为屏幕不需要往服务端发数据。但我选WebSocket的原因是后续要加屏幕心跳上报和远程控制屏幕切换页面的功能SSE做不了。另外工厂网络环境复杂WebSocket的二进制帧在某些老旧的工业交换机上表现更稳定。注意如果现场网络有代理或防火墙WebSocket的握手可能被拦截需要提前确认网络策略。SSE基于HTTP穿透性更好但功能受限。2.3 数据聚合层的设计要点中间层我用了Node.js写了一个轻量服务核心逻辑是定时从MES的REST接口拉取数据间隔可配置默认5秒数据做归一化处理统一字段名和单位通过WebSocket广播给所有连接的屏幕维护一个当前数据快照新连接的屏幕立即收到最新数据这里有个关键设计拉取间隔和推送间隔分离。拉取可以5秒一次但推送只在数据发生变化时触发。如果MES数据没变就不推减少网络流量和屏幕重绘。实测下来注塑机状态这种变化频率低的数据一天推送次数不到200次而产量数据变化频繁推送次数会多很多。// 数据聚合服务核心逻辑示意 const WebSocket require(ws); const wss new WebSocket.Server({ port: 8080 }); let currentSnapshot {}; // 定时拉取MES数据 setInterval(async () { const newData await fetchFromMES(); const normalized normalize(newData); // 只在数据变化时推送 if (JSON.stringify(normalized) ! JSON.stringify(currentSnapshot)) { currentSnapshot normalized; broadcast(currentSnapshot); } }, 5000); function broadcast(data) { const message JSON.stringify({ type: update, payload: data }); wss.clients.forEach(client { if (client.readyState WebSocket.OPEN) { client.send(message); } }); } // 新连接立即发送快照 wss.on(connection, ws { ws.send(JSON.stringify({ type: snapshot, payload: currentSnapshot })); });这段代码看起来简单但实际部署时要考虑MES接口超时、数据格式异常、WebSocket连接数上限等问题。我加了重试机制和错误日志后面会细讲。3. NTP校时被大多数人忽略的同步基石3.1 为什么工厂环境必须做NTP工厂里的设备时钟漂移比办公室严重得多。原因包括工控机长时间不关机、电网频率波动影响晶振、车间温度变化大。我见过一台工控机运行三个月后慢了将近两分钟。如果看板上显示最后更新时间14:30:00而实际时间是14:32:00操作工会认为系统卡死了。NTP校时的作用就是让所有设备——包括MES服务器、数据聚合服务、三块屏幕的播放终端——都对齐到同一个时间源。这样即使数据推送有微小延迟时间戳也是一致的不会出现数据一样但时间对不上的困惑。3.2 校时方案的实际选择工厂内网通常不能直接访问外网NTP服务器所以需要在本地部署一个NTP服务。常见做法是用一台Linux服务器做NTP Server其他设备指向它。如果工厂有华为云或其他云服务也可以用云厂商提供的NTP服务地址但要注意内网穿透和延迟问题。我当时的方案是数据聚合服务所在的工控机Ubuntu 20.04作为NTP Server三块屏幕的播放终端分别是两台Windows小主机和一台安卓广告机作为NTP Client工控机本身向上级NTP源同步Windows端校时命令# 查看当前时间源 w32tm /query /source # 配置NTP服务器 w32tm /config /manualpeerlist:192.168.1.100 /syncfromflags:manual /reliable:yes /update # 立即同步 w32tm /resync # 查看同步状态 w32tm /query /statusLinux端校时# 安装ntpdate sudo apt install ntpdate # 手动同步一次 sudo ntpdate 192.168.1.100 # 配置定时同步 echo */5 * * * * /usr/sbin/ntpdate 192.168.1.100 | sudo crontab -安卓广告机比较麻烦很多安卓设备没有root权限无法直接改NTP配置。我的做法是在播放页面里用JavaScript获取服务端时间然后计算本地时间偏移量在渲染时做补偿。这样即使设备本身时间不准显示出来的时间也是对的。3.3 校时频率与精度取舍NTP校时不是越频繁越好。过于频繁的校时会导致时间跳变影响依赖时间戳的业务逻辑。我的经验是工控机作为NTP Server每64秒向上级同步一次屏幕终端每5分钟向工控机同步一次如果对时间精度要求极高比如需要毫秒级对齐可以考虑PTP协议但成本和复杂度会大幅上升提示校时后一定要验证。我遇到过Windows防火墙拦截NTP端口导致校时失败的情况表面上看配置都对了实际时间根本没同步。验证方法是手动改一下本地时间然后执行同步命令看是否恢复。4. MES数据对接从接口到看板的完整链路4.1 MES接口的常见形态与对接策略汽车水冷板工厂用的MES是国内某厂商的成品系统对外提供REST接口。但实际对接时发现几个问题第一接口文档不全。厂商只给了几个主要接口的说明字段含义模糊有些字段的值域没有定义。我的做法是先用Postman把能调的接口都调一遍把返回的JSON结构记录下来再结合车间实际业务反推字段含义。第二接口性能不稳定。高峰期调用延迟超过3秒偶尔还会超时。所以数据聚合服务必须做超时控制和重试。我设置的是单次请求超时2秒失败后重试2次重试间隔1秒。如果三次都失败就沿用上一次的数据并在日志里记录告警。第三数据格式不统一。有的接口返回时间戳是毫秒有的是秒有的用字符串表示数字有的用数值类型。归一化层要处理这些差异。// 数据归一化示例 function normalize(raw) { return { timestamp: normalizeTimestamp(raw.ts), output: parseNumber(raw.output_count), defectRate: parseFloat(raw.defect_rate) || 0, machineStatus: mapStatus(raw.status_code), // ... 其他字段 }; } function normalizeTimestamp(ts) { // 兼容秒和毫秒 if (ts 1e12) return ts; return ts * 1000; } function parseNumber(val) { if (typeof val number) return val; return parseInt(val, 10) || 0; }4.2 数据刷新频率的确定刷新频率不是拍脑袋定的要考虑三个因素MES数据更新频率、网络带宽、人眼感知。注塑机状态变化不频繁5秒刷新足够产量数据变化快但操作工不需要看到每一次跳动3秒刷新比较合适不良率数据变化慢10秒刷新都行。最终我用了分级刷新策略不同数据字段有不同的推送阈值变化超过阈值才推。具体配置数据项拉取间隔推送阈值说明注塑机状态5秒状态变化即推状态只有几种变化不频繁当班产量3秒变化超过5件避免频繁跳动不良率10秒变化超过0.1%变化慢阈值可大设备OEE30秒变化超过1%计算复杂更新慢这个策略实测下来网络流量比全量推送降低了约70%屏幕渲染压力也小了很多。4.3 异常数据的处理与展示MES数据偶尔会出现异常值比如产量突然变成负数或者不良率超过100%。这些数据如果直接显示在看板上操作工会以为系统坏了。我的处理方式是服务端做数据校验异常值不推送沿用上一次有效值看板上用灰色显示数据更新中而不是显示错误数字同时在企业微信或钉钉上发告警给运维人员这里有个细节不要在看板上显示错误或异常字样。车间环境里操作工看到错误会紧张可能触发不必要的停机检查。用数据更新中更温和也符合实际情况——数据确实在更新只是暂时没拿到。5. 大屏适配与渲染优化让三块屏看起来像一块5.1 分辨率与布局的适配策略三块屏的物理分辨率不一样两块是1920x1080一块是3840x2160的4K屏。如果直接用同一套页面4K屏上字会特别小1080p屏上又可能溢出。我的做法是用响应式布局动态缩放页面用rem或vw/vh做单位根据屏幕宽度动态计算根字号关键数据区域用flex布局自动填充可用空间设置最小字号和最大字号防止极端情况下不可读/* 动态根字号计算 */ html { font-size: calc(100vw / 1920 * 16); } /* 限制范围 */ media (max-width: 1280px) { html { font-size: 12px; } } media (min-width: 2560px) { html { font-size: 24px; } }这样在1080p屏上根字号是16px在4K屏上自动变成32px布局比例保持一致。5.2 渲染性能的优化看板页面通常要长时间运行内存泄漏和渲染卡顿是常见问题。我踩过的坑包括定时器未清理页面切换时旧的定时器还在跑导致内存持续增长。解决方法是所有定时器在组件卸载时清除。频繁重绘数据更新时整个页面重绘导致闪烁。解决方法是只更新变化的DOM节点用虚拟DOM或手动diff。动画过多数字滚动动画虽然好看但三块屏同时跑动画会占用大量CPU。后来改成只在数据变化时做一次简单过渡不做持续动画。注意安卓广告机的浏览器内核版本通常较老某些CSS属性不支持。建议用Can I Use查一下兼容性或者直接用最基础的CSS。5.3 断线重连与离线展示工厂网络不是100%可靠的偶尔会断网。看板必须能处理断线情况WebSocket断开后前端自动重连重连间隔指数退避1秒、2秒、4秒、8秒...最大30秒重连期间显示连接中状态但保留最后一次的数据如果超过一定时间比如5分钟没连上显示网络异常请检查// 断线重连逻辑 let reconnectDelay 1000; const MAX_DELAY 30000; function connect() { const ws new WebSocket(ws://192.168.1.100:8080); ws.onopen () { reconnectDelay 1000; // 重置 updateStatus(connected); }; ws.onclose () { updateStatus(disconnected); setTimeout(connect, reconnectDelay); reconnectDelay Math.min(reconnectDelay * 2, MAX_DELAY); }; ws.onmessage (event) { const data JSON.parse(event.data); render(data); }; }这个逻辑看起来简单但实际调试时发现一个问题如果服务端重启所有屏幕同时重连会造成瞬时连接风暴。后来加了随机抖动reconnectDelay Math.random() * 1000把重连时间打散。6. 调试实战从串口工具到网络抓包的完整排查链路6.1 硬件层的排查网口调试助手的使用项目初期遇到过一个诡异问题三块屏中有一块偶尔收不到数据但另外两块正常。排查过程如下第一步用网口调试助手比如sscom或类似的TCP/UDP调试工具在那块屏的网口上抓包发现数据包确实到达了设备但应用层没处理。说明问题不在网络层在应用层。第二步检查那块屏的播放终端发现是安卓系统WebSocket库版本较老对某些帧格式支持不好。换成轮询方式后正常。第三步为了确认不是网络问题用ping和traceroute测试了到服务端的延迟和丢包率结果都正常。这个排查链路的关键是分层定位先确认物理层和网络层没问题再往上查应用层。网口调试助手在这里的作用是确认数据包是否到达设备如果到达了但没处理就是应用的问题如果没到达就要查交换机和网线。6.2 服务端的日志与监控数据聚合服务的日志我分了三个级别INFO正常的数据拉取和推送记录WARNMES接口超时、数据格式异常ERRORWebSocket连接失败、服务启动失败日志用winston写到文件同时输出到控制台。关键是要记录每次推送的数据摘要方便事后对账。比如[2024-10-15 14:30:05] INFO: Push update - output: 1250, defectRate: 2.3%, clients: 3 [2024-10-15 14:30:08] WARN: MES timeout, using last snapshot [2024-10-15 14:30:10] INFO: Push update - output: 1253, defectRate: 2.3%, clients: 3这样如果操作工反馈14:30左右数据不对我可以直接查日志看当时推的是什么数据。6.3 前端调试的实用技巧看板前端跑在浏览器里调试可以用Chrome的开发者工具但车间里的设备不一定方便接键盘鼠标。我的做法是在页面上加一个隐藏的调试面板连续点击角落5次触发调试面板显示WebSocket连接状态、最后收到数据的时间、当前渲染的数据快照支持手动触发重连和数据刷新这个面板在验收时特别有用客户看到系统有自检能力信任度会高很多。另外如果现场有多个屏幕可以在URL里加参数区分比如?screen1这样服务端可以针对不同屏幕做差异化推送虽然本项目没用上但预留了扩展能力。7. 验收与交付让客户自己会排查7.1 验收标准的量化验收不能只看能显示要量化指标指标标准实测数据同步延迟三块屏差异1秒平均0.3秒刷新频率产量数据3秒内更新2.8秒断线恢复时间网络恢复后10秒内重连6秒连续运行稳定性72小时无重启通过时间同步精度三块屏时间差1秒0.2秒这些指标在验收报告里写清楚客户签字也放心。7.2 交付文档与培训交付时给了三份文档部署文档怎么安装、怎么配置、怎么启动运维手册常见问题排查、日志查看、重启步骤接口文档MES接口的字段说明和数据字典培训花了半天重点教了两个人设备科的电工和IT运维。电工负责硬件和网络IT负责服务和数据。培训时特意让他们自己动手操作一遍包括重启服务、查看日志、模拟断网恢复。7.3 后续扩展的预留项目交付后客户提了几个新需求加一块屏、增加设备OEE展示、对接企业微信告警。因为前期设计时预留了扩展点这些需求实现起来都不难加屏新屏幕连上WebSocket即可服务端不用改加数据在归一化层加字段前端加渲染逻辑告警服务端加一个webhook调用提示前期设计时多留一点扩展空间后期改起来省事很多。但也不要过度设计够用就行。8. 个人经验那些文档里不会写的坑最后分享几个实际踩过的坑都是文档里不会写的坑一交换机端口协商问题。有一块屏偶尔断连换了网线也不行。后来发现是交换机端口速率协商有问题强制设成100M全双工后稳定了。工业交换机有时候自动协商会抽风。坑二浏览器缓存导致旧页面。更新了看板页面后有一块屏还是显示旧版。原因是浏览器缓存了旧文件。解决方法是给静态资源加版本号或者设置Cache-Control: no-cache。坑三MES接口的时区问题。MES返回的时间戳是UTC但看板要显示本地时间。一开始没注意显示的时间差了8小时。后来在归一化层统一转成东八区。坑四安卓广告机的休眠策略。安卓设备默认会休眠导致看板黑屏。需要在系统设置里关闭休眠或者用代码保持屏幕常亮。有些设备还需要在开发者选项里开启充电时不休眠。坑五NTP校时被防火墙拦截。Windows防火墙默认会拦截NTP的UDP 123端口。配置校时后一定要验证方法是在客户端执行w32tm /stripchart /computer:192.168.1.100看是否能收到时间戳。这些坑单个看起来都不大但凑在一起能让调试时间翻倍。我的建议是每解决一个问题就在运维手册里记一笔下次遇到类似问题直接查手册不用重新排查。整套系统跑到现在快一年了中间只重启过两次服务一次是MES升级一次是工控机系统更新。客户反馈说现在交接班不用拍照对数据了站在车间中间抬头看一眼就行。这大概就是做工业看板最有成就感的地方——技术不复杂但确实解决了实际问题。