// 模拟工业以太网/5G 网关数据接入与远程控制(前端造数) // 设备围绕煤炭加工场景:带式输送机、破碎机、皮带秤、除尘风机、给煤机等 const DEFAULT_THRESHOLDS = { temperatureC: 85, // ℃ pressureBar: 12, // bar currentA: 180, // A voltageV: 420, // V(三相线电压) powerFactor: 0.85 // cosφ } const STORAGE_KEYS = { thresholds: 'monitor_equipment_thresholds', channels: 'monitor_equipment_alarm_channels' } const DEFAULT_CHANNELS = { platform: true, sms: false, voice: false } const BASE_EQUIPMENTS = [ { id: 'conv-01', name: '主煤流带式输送机#1', location: '筛分车间一线', type: 'conveyor' }, { id: 'crusher-01', name: '齿辊破碎机#1', location: '破碎工段', type: 'crusher' }, { id: 'feeder-01', name: '电机振动给煤机#1', location: '原煤仓下口', type: 'feeder' }, { id: 'blower-01', name: '除尘离心风机#1', location: '装车除尘点', type: 'blower' }, { id: 'scale-01', name: '皮带秤#1', location: '计量段', type: 'beltScale' } ] function getRandomAround(base, fluct = 0.05) { const delta = base * fluct return +(base + (Math.random() * 2 - 1) * delta).toFixed(2) } function seededInitMetrics(type) { switch (type) { case 'conveyor': return { temperatureC: 55, pressureBar: 6.5, currentA: 95, voltageV: 400, powerFactor: 0.92 } case 'crusher': return { temperatureC: 65, pressureBar: 10.5, currentA: 140, voltageV: 405, powerFactor: 0.9 } case 'feeder': return { temperatureC: 50, pressureBar: 5.5, currentA: 60, voltageV: 398, powerFactor: 0.93 } case 'blower': return { temperatureC: 70, pressureBar: 8.2, currentA: 120, voltageV: 402, powerFactor: 0.88 } case 'beltScale': return { temperatureC: 48, pressureBar: 0.0, currentA: 35, voltageV: 401, powerFactor: 0.95 } default: return { temperatureC: 55, pressureBar: 6, currentA: 90, voltageV: 400, powerFactor: 0.9 } } } function nextMetrics(current, type) { const drift = { temperatureC: getRandomAround(current.temperatureC, 0.03), pressureBar: getRandomAround(current.pressureBar, 0.06), currentA: getRandomAround(current.currentA, 0.08), voltageV: getRandomAround(current.voltageV, 0.01), powerFactor: Math.max(0.6, Math.min(0.99, getRandomAround(current.powerFactor, 0.02))) } if (type === 'crusher') drift.currentA += Math.random() * 4 - 2 if (type === 'blower') drift.powerFactor -= Math.random() * 0.01 return { temperatureC: +drift.temperatureC.toFixed(2), pressureBar: +drift.pressureBar.toFixed(2), currentA: +drift.currentA.toFixed(2), voltageV: +drift.voltageV.toFixed(2), powerFactor: +drift.powerFactor.toFixed(2) } } export function getThresholds() { try { const raw = localStorage.getItem(STORAGE_KEYS.thresholds) if (!raw) return { ...DEFAULT_THRESHOLDS } const obj = JSON.parse(raw) return { ...DEFAULT_THRESHOLDS, ...obj } } catch (e) { return { ...DEFAULT_THRESHOLDS } } } export function saveThresholds(thresholds) { const merged = { ...DEFAULT_THRESHOLDS, ...thresholds } localStorage.setItem(STORAGE_KEYS.thresholds, JSON.stringify(merged)) return merged } export function getAlarmChannels() { try { const raw = localStorage.getItem(STORAGE_KEYS.channels) if (!raw) return { ...DEFAULT_CHANNELS } const obj = JSON.parse(raw) return { ...DEFAULT_CHANNELS, ...obj } } catch (e) { return { ...DEFAULT_CHANNELS } } } export function saveAlarmChannels(channels) { const merged = { ...DEFAULT_CHANNELS, ...channels } localStorage.setItem(STORAGE_KEYS.channels, JSON.stringify(merged)) return merged } // 订阅实时数据(模拟低时延、高可靠链路) export function subscribeEquipmentData(callback, options = {}) { const { intervalMs = 1000, streaming = true } = options let isStopped = false const equipmentStates = BASE_EQUIPMENTS.map(e => ({ ...e, status: 'RUNNING', metrics: seededInitMetrics(e.type) })) // 非流模式:仅推送一次快照 if (!streaming) { const now = Date.now() equipmentStates.forEach(es => { callback({ ts: now, equipmentId: es.id, name: es.name, location: es.location, status: es.status, metrics: { ...es.metrics } }) }) return () => {} } function tick() { if (isStopped) return const now = Date.now() equipmentStates.forEach(es => { if (Math.random() < 0.01) { es.status = es.status === 'RUNNING' ? 'STOPPED' : 'RUNNING' } if (es.status === 'RUNNING') { es.metrics = nextMetrics(es.metrics, es.type) } else { es.metrics = { ...es.metrics, currentA: +(es.metrics.currentA * 0.1).toFixed(2), powerFactor: Math.max(0.5, +(es.metrics.powerFactor * 0.8).toFixed(2)) } } callback({ ts: now, equipmentId: es.id, name: es.name, location: es.location, status: es.status, metrics: { ...es.metrics } }) }) timer = setTimeout(tick, intervalMs) } let timer = setTimeout(tick, intervalMs) return () => { isStopped = true if (timer) clearTimeout(timer) } } export function sendControlCommand(equipmentId, action) { return new Promise(resolve => { const delay = 50 + Math.round(Math.random() * 70) setTimeout(() => { resolve({ code: 200, msg: 'OK', data: { equipmentId, action, acceptedAt: Date.now() } }) /* eslint-disable no-console */ console.log(`[CONTROL] equipment=${equipmentId} action=${action}`) }, delay) }) } export function detectAlarms(metrics, thresholds) { const over = [] if (metrics.temperatureC > thresholds.temperatureC) over.push({ field: 'temperatureC', value: metrics.temperatureC, threshold: thresholds.temperatureC }) if (metrics.pressureBar > thresholds.pressureBar) over.push({ field: 'pressureBar', value: metrics.pressureBar, threshold: thresholds.pressureBar }) if (metrics.currentA > thresholds.currentA) over.push({ field: 'currentA', value: metrics.currentA, threshold: thresholds.currentA }) if (metrics.voltageV > thresholds.voltageV) over.push({ field: 'voltageV', value: metrics.voltageV, threshold: thresholds.voltageV }) if (metrics.powerFactor < thresholds.powerFactor) over.push({ field: 'powerFactor', value: metrics.powerFactor, threshold: thresholds.powerFactor }) return over } export function listEquipments() { return BASE_EQUIPMENTS.map(e => ({ id: e.id, name: e.name, location: e.location, type: e.type })) } export default { subscribeEquipmentData, sendControlCommand, detectAlarms, getThresholds, saveThresholds, getAlarmChannels, saveAlarmChannels, listEquipments }