前言
在智能制造和工业 4.0 的浪潮下,越来越多的工厂车间需要实时监控设备的运行状态。三色报警灯(红、黄、绿)作为工业现场最直观的"视觉语言",配合蜂鸣器发出的声音告警,能够第一时间通知现场人员设备出现了什么问题。
传统的三色灯通常直接连接到 PLC 的数字输出点,由 PLC 根据设备状态直接控制。但在很多智能化改造场景中,我们希望前端 Web 应用能够实时采集设备运行参数,并根据预设的阈值规则远程控制三色蜂鸣灯的报警状态。这就面临一个问题:三色灯不与设备机器直接通信,而是通过网关接入网络。
本文将从前端工程师的视角,详细介绍如何通过 WebSocket 实时接收设备采集数据,并结合网关的 Modbus TCP 协议实现三色蜂鸣灯的远程报警控制。全文涵盖系统架构设计、前端核心代码实现、协议解析和性能优化,力求提供一套可落地的技术方案。
一、系统架构概览
在正式写代码之前,先理清整个系统的数据流向:
scss
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 设备传感器 │────▶│ 边缘网关 │────▶│ 后端服务 │────▶│ 前端 Web │
│ (温度/振动等)│ │ (Modbus/MQTT)│ │ (WebSocket) │ │ (实时监控) │
└─────────────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 三色蜂鸣灯 │◀────│ 控制指令 │◀────│ 阈值判断 │
│(Modbus TCP) │ │ (HTTP/WS) │ │ (前端逻辑) │
└─────────────┘ └─────────────┘ └─────────────┘
核心组件说明:
- 设备传感器:采集设备运行参数,如温度、振动频率、转速、电流等,通过串口、网口或无线方式上报给网关。
- 边缘网关:负责协议转换和数据汇聚,将传感器数据统一封装后上报后端,同时接收后端下发的控制指令,通过 Modbus TCP 协议驱动三色蜂鸣灯。
- 后端服务:提供 WebSocket 服务端,负责实时推送设备数据到前端,并接收前端的报警控制指令。
- 前端 Web 应用:接收 WebSocket 实时数据流,与预设的标准阈值对比,当参数超限时触发报警逻辑,向网关发送三色蜂鸣灯的控制指令。
- 三色蜂鸣灯:通过 Modbus TCP 协议接入网关,根据指令显示红/黄/绿状态并触发蜂鸣器。
二、通信协议选型
2.1 数据上报:WebSocket 实时推送
设备监控场景对实时性要求极高,轮询方式不仅浪费带宽,还存在延迟。WebSocket 建立长连接后,服务端可以主动向客户端推送数据,实现毫秒级的实时响应。
选型要点:
- 原生 WebSocket 协议简单,前端
new WebSocket()即可使用,无需额外库。 - 需要心跳机制保持连接,防止防火墙或代理自动断开。
- 在复杂场景下可以考虑 Socket.IO 或 Netty-SocketIO,它们提供了自动重连、房间管理、降级轮询等高级特性。
2.2 设备控制:Modbus TCP 协议
三色蜂鸣灯通过网关接入网络后,通常支持 Modbus TCP 协议进行远程控制。泓格科技的 ALM-Horn-RGB 等工业声光警报器就是典型的 Modbus TCP 设备,支持通过网络远程设置 LED 颜色和蜂鸣器状态。
Modbus TCP 的核心概念:
| 概念 | 说明 |
|---|---|
| 从站地址(Unit ID) | 设备的唯一标识,通常为 1~247 |
| 功能码 | 03 读保持寄存器,06 写单个寄存器,16 写多个寄存器 |
| 寄存器地址 | 每个控制功能对应一个寄存器地址,如颜色控制寄存器、蜂鸣器控制寄存器 |
| 数据值 | 写入寄存器的具体值,如 0x01=红色、0x02=黄色、0x03=绿色 |
例如,向某个三色灯发送红色常亮指令,可能对应功能码 06(写单个寄存器),寄存器地址 0x0000,数据值 0x0001。
三、前端核心实现
3.1 建立 WebSocket 连接
javascript
class DeviceMonitor {
constructor(options) {
this.wsUrl = options.wsUrl || 'ws://localhost:8080/device-data';
this.reconnectInterval = options.reconnectInterval || 3000;
this.heartbeatInterval = options.heartbeatInterval || 30000;
this.thresholds = options.thresholds || {}; // 阈值配置
this.onAlarm = options.onAlarm; // 报警回调
this.init();
}
init() {
this.connect();
this.bindEvents();
}
connect() {
this.ws = new WebSocket(this.wsUrl);
this.ws.onopen = () => {
console.log('[DeviceMonitor] WebSocket 连接成功');
this.startHeartbeat();
// 连接成功后可以发送订阅消息
this.send({
type: 'subscribe',
devices: this.deviceIds
});
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleDeviceData(data);
} catch (err) {
console.error('[DeviceMonitor] 数据解析失败', err);
}
};
this.ws.onerror = (error) => {
console.error('[DeviceMonitor] WebSocket 错误', error);
};
this.ws.onclose = () => {
console.log('[DeviceMonitor] WebSocket 连接关闭,尝试重连...');
this.stopHeartbeat();
this.reconnect();
};
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send({ type: 'ping', timestamp: Date.now() });
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
reconnect() {
if (this.reconnectTimer) return;
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
this.connect();
}, this.reconnectInterval);
}
}
3.2 数据处理与阈值判断
接收到设备数据后,需要与预设的标准阈值进行对比,决定是否触发报警:
javascript
// 报警等级定义
const AlarmLevel = {
NORMAL: { level: 0, color: 'green', buzzer: false },
WARNING: { level: 1, color: 'yellow', buzzer: false },
CRITICAL: { level: 2, color: 'red', buzzer: true }
};
// 阈值配置示例
const THRESHOLD_CONFIG = {
temperature: {
warning: { min: 60, max: 75 },
critical: { min: 75, max: 100 }
},
vibration: {
warning: { min: 4.5, max: 8.0 },
critical: { min: 8.0, max: 20.0 }
},
current: {
warning: { min: 15, max: 25 },
critical: { min: 25, max: 50 }
}
};
class AlarmEngine {
constructor(thresholds) {
this.thresholds = thresholds;
this.currentAlarmLevel = AlarmLevel.NORMAL;
this.alarmHistory = [];
}
evaluate(deviceData) {
const { temperature, vibration, current } = deviceData;
// 检查是否有任何参数达到严重告警
if (this.isCritical(temperature, 'temperature') ||
this.isCritical(vibration, 'vibration') ||
this.isCritical(current, 'current')) {
return AlarmLevel.CRITICAL;
}
// 检查是否有任何参数达到警告告警
if (this.isWarning(temperature, 'temperature') ||
this.isWarning(vibration, 'vibration') ||
this.isWarning(current, 'current')) {
return AlarmLevel.WARNING;
}
return AlarmLevel.NORMAL;
}
isWarning(value, param) {
const config = this.thresholds[param];
if (!config || !config.warning) return false;
const { min, max } = config.warning;
return value >= min && value < max;
}
isCritical(value, param) {
const config = this.thresholds[param];
if (!config || !config.critical) return false;
const { min, max } = config.critical;
return value >= min && value < max;
}
// 避免告警抖动,增加防抖逻辑
shouldTriggerAlarm(newLevel) {
// 如果告警等级上升,立即触发
if (newLevel.level > this.currentAlarmLevel.level) {
return true;
}
// 如果告警等级下降,需要连续 N 次确认后才恢复
if (newLevel.level < this.currentAlarmLevel.level) {
// 这里可以实现连续确认逻辑
return false;
}
return false;
}
}
3.3 触发报警控制
当检测到参数超限时,前端通过 HTTP 请求向后端发送报警控制指令,后端再通过网关的 Modbus TCP 接口驱动三色蜂鸣灯:
javascript
class AlarmController {
constructor(apiBase) {
this.apiBase = apiBase;
this.currentState = null;
}
async triggerAlarm(deviceId, level) {
// 防止重复发送相同状态
if (this.currentState &&
this.currentState.deviceId === deviceId &&
this.currentState.level === level) {
return;
}
const command = this.buildAlarmCommand(level);
try {
const response = await fetch(`${this.apiBase}/alarm/trigger`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
deviceId,
color: command.color,
pattern: command.pattern, // 'on' | 'blink_slow' | 'blink_fast'
buzzer: command.buzzer,
buzzerPattern: command.buzzerPattern
})
});
const result = await response.json();
if (result.success) {
this.currentState = { deviceId, level };
console.log(`[Alarm] 报警已触发,设备: ${deviceId},等级: ${level}`);
// 记录告警日志
this.logAlarm(deviceId, level);
}
} catch (error) {
console.error('[Alarm] 报警触发失败', error);
// 可以考虑加入重试机制
this.retryTrigger(deviceId, level);
}
}
buildAlarmCommand(level) {
switch (level) {
case AlarmLevel.CRITICAL:
return {
color: 'red',
pattern: 'blink_fast',
buzzer: true,
buzzerPattern: 'intermittent'
};
case AlarmLevel.WARNING:
return {
color: 'yellow',
pattern: 'on',
buzzer: false
};
default:
return {
color: 'green',
pattern: 'on',
buzzer: false
};
}
}
async resetAlarm(deviceId) {
return this.triggerAlarm(deviceId, AlarmLevel.NORMAL);
}
logAlarm(deviceId, level) {
// 上报告警事件到日志系统
console.log(`[AlarmLog] ${new Date().toISOString()} | Device: ${deviceId} | Level: ${level}`);
}
}
3.4 完整监控组件整合
javascript
// 使用示例:React 组件中的整合
import { useState, useEffect, useRef } from 'react';
function DeviceMonitorDashboard({ deviceId, thresholds }) {
const [deviceData, setDeviceData] = useState(null);
const [alarmLevel, setAlarmLevel] = useState(AlarmLevel.NORMAL);
const [connectionStatus, setConnectionStatus] = useState('disconnected');
const monitorRef = useRef(null);
const alarmControllerRef = useRef(null);
const alarmEngineRef = useRef(null);
useEffect(() => {
// 初始化报警引擎
alarmEngineRef.current = new AlarmEngine(thresholds);
alarmControllerRef.current = new AlarmController('/api/v1');
// 初始化设备监控
monitorRef.current = new DeviceMonitor({
wsUrl: `ws://gateway.local:8080/device/${deviceId}/stream`,
thresholds,
onAlarm: (level) => {
setAlarmLevel(level);
alarmControllerRef.current.triggerAlarm(deviceId, level);
}
});
// 扩展 onmessage 处理
const originalOnMessage = monitorRef.current.ws.onmessage;
monitorRef.current.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setDeviceData(data);
// 阈值判断
const newLevel = alarmEngineRef.current.evaluate(data);
if (alarmEngineRef.current.shouldTriggerAlarm(newLevel)) {
setAlarmLevel(newLevel);
alarmControllerRef.current.triggerAlarm(deviceId, newLevel);
}
// 调用原始处理
if (originalOnMessage) originalOnMessage(event);
};
return () => {
// 清理连接
monitorRef.current?.ws?.close();
};
}, [deviceId, thresholds]);
return (
<div className="device-monitor">
<div className={`status-indicator ${alarmLevel.color}`}>
<span>当前状态: {alarmLevel.level === 0 ? '正常' : alarmLevel.level === 1 ? '警告' : '严重告警'}</span>
</div>
{deviceData && (
<div className="data-panel">
<div>温度: {deviceData.temperature}°C</div>
<div>振动: {deviceData.vibration} mm/s</div>
<div>电流: {deviceData.current} A</div>
</div>
)}
<div className="connection-status">
连接状态: {connectionStatus}
</div>
</div>
);
}
四、后端网关通信实现(简述)
虽然本文聚焦前端,但为了让整体方案更完整,这里简述后端如何与网关交互控制三色蜂鸣灯:
javascript
// Node.js 后端示例:通过 Modbus TCP 控制三色灯
const ModbusRTU = require('modbus-serial');
class ModbusAlarmController {
constructor(options) {
this.host = options.host;
this.port = options.port || 502; // Modbus TCP 默认端口
this.unitId = options.unitId || 1;
this.client = new ModbusRTU();
}
async connect() {
await this.client.connectTCP(this.host, { port: this.port });
this.client.setID(this.unitId);
console.log(`[Modbus] 已连接到 ${this.host}:${this.port}`);
}
// 设置 LED 颜色
// 假设寄存器 0x0000 控制颜色:0=关, 1=红, 2=黄, 3=绿
async setColor(color) {
const colorMap = { red: 1, yellow: 2, green: 3, off: 0 };
const value = colorMap[color] || 0;
await this.client.writeRegister(0x0000, value);
console.log(`[Modbus] 设置颜色: ${color} (${value})`);
}
// 设置闪烁模式
// 假设寄存器 0x0001 控制闪烁:0=常亮, 1=慢闪, 2=快闪
async setBlinkPattern(pattern) {
const patternMap = { on: 0, blink_slow: 1, blink_fast: 2 };
const value = patternMap[pattern] || 0;
await this.client.writeRegister(0x0001, value);
}
// 控制蜂鸣器
// 假设寄存器 0x0002 控制蜂鸣器:0=关, 1=开
async setBuzzer(enabled) {
await this.client.writeRegister(0x0002, enabled ? 1 : 0);
}
async close() {
this.client.close();
}
}
// Express 路由示例
app.post('/api/v1/alarm/trigger', async (req, res) => {
const { deviceId, color, pattern, buzzer } = req.body;
try {
const controller = getModbusController(deviceId);
await controller.setColor(color);
await controller.setBlinkPattern(pattern);
await controller.setBuzzer(buzzer);
res.json({ success: true, message: '报警指令已下发' });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
五、WebSocket 连接优化策略
工业现场网络环境复杂,WebSocket 连接的稳定性直接影响报警系统的可靠性。以下是一些经过验证的优化策略:
5.1 心跳保活
WebSocket 连接可能因网络波动、防火墙策略等原因被中断。心跳机制是保持连接的有效手段:
javascript
class HeartbeatManager {
constructor(ws, options = {}) {
this.ws = ws;
this.pingInterval = options.pingInterval || 30000;
this.pongTimeout = options.pongTimeout || 10000;
this.timer = null;
this.pongTimer = null;
}
start() {
this.timer = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
// 设置 pong 超时检测
this.pongTimer = setTimeout(() => {
console.warn('[Heartbeat] Pong 超时,主动断开连接');
this.ws.close();
}, this.pongTimeout);
}
}, this.pingInterval);
}
onPong() {
if (this.pongTimer) {
clearTimeout(this.pongTimer);
this.pongTimer = null;
}
}
stop() {
if (this.timer) clearInterval(this.timer);
if (this.pongTimer) clearTimeout(this.pongTimer);
this.timer = null;
this.pongTimer = null;
}
}
5.2 指数退避重连
网络断开后,不宜立即高频重连,应采用指数退避策略:
javascript
class ReconnectScheduler {
constructor(maxDelay = 60000) {
this.attempt = 0;
this.maxDelay = maxDelay;
}
nextDelay() {
// 指数退避:2^attempt * 1000,最大 60 秒
const delay = Math.min(1000 * Math.pow(2, this.attempt), this.maxDelay);
this.attempt++;
return delay;
}
reset() {
this.attempt = 0;
}
}
5.3 数据去重与乱序处理
WebSocket 数据推送可能在重连后出现重复或乱序。可以在数据包中加入序列号:
javascript
class DataStreamHandler {
constructor() {
this.lastSeq = 0;
this.dataBuffer = new Map();
}
handle(data) {
const { seq, timestamp, payload } = data;
// 丢弃旧数据
if (seq <= this.lastSeq) {
console.warn(`[Stream] 丢弃重复/过期数据 seq=${seq}`);
return null;
}
this.lastSeq = seq;
return payload;
}
}
六、常见问题与解决方案
6.1 浏览器兼容性
WebSocket 在所有现代浏览器中都有良好支持。但如果需要兼容 IE 等老旧浏览器,可以考虑使用 Socket.IO,它提供了自动降级到长轮询的能力。
6.2 网关指令下发延迟
Modbus TCP 通信本身存在网络延迟,加上网关处理时间,指令下发到三色灯亮起可能有 100~500ms 的延迟。在阈值判断时应考虑这一延迟,可以适当设置报警触发缓冲时间,避免因瞬时波动频繁触发。
6.3 多设备并发控制
当监控多个设备时,需要注意网关的并发处理能力。建议在前端实现指令队列,避免短时间大量指令同时下发导致网关过载:
javascript
class CommandQueue {
constructor(interval = 100) {
this.queue = [];
this.interval = interval;
this.running = false;
}
enqueue(command) {
return new Promise((resolve, reject) => {
this.queue.push({ command, resolve, reject });
this.run();
});
}
async run() {
if (this.running) return;
this.running = true;
while (this.queue.length > 0) {
const { command, resolve, reject } = this.queue.shift();
try {
const result = await this.execute(command);
resolve(result);
} catch (err) {
reject(err);
}
await this.sleep(this.interval);
}
this.running = false;
}
}
6.4 报警状态持久化
当 WebSocket 断连时,前端无法接收到最新数据,但三色灯应该保持最后的状态。建议在后端维护报警状态的持久化记录,前端重连后同步最新状态。
七、总结
本文从前端工程师的视角,详细介绍了如何通过 WebSocket 实时接收设备采集数据,并结合网关的 Modbus TCP 协议驱动三色蜂鸣灯实现远程报警。核心要点回顾:
- 通信架构:设备数据通过 WebSocket 推送到前端,前端判断阈值后通过 HTTP 下发报警指令,后端再通过 Modbus TCP 控制三色灯。
- 协议选型:WebSocket 保证数据实时性,Modbus TCP 实现工业设备的标准化控制。
- 前端核心逻辑:阈值判断 + 告警去抖 + 状态防重复,保证报警的准确性和稳定性。
- 可靠性保障:心跳保活、指数退避重连、数据去重、指令队列,应对工业现场复杂的网络环境。
随着工业物联网的不断发展,前端工程师在工业智能化场景中的参与度会越来越高。掌握 WebSocket 实时通信和工业协议的基本概念,将帮助前端工程师更好地融入智能制造的技术生态。
希望本文能为正在做工业监控项目的同学提供一些参考和启发。欢迎在评论区交流讨论!