diff --git a/server/src/demo.js b/server/src/demo.js index 04be9ff..379c167 100644 --- a/server/src/demo.js +++ b/server/src/demo.js @@ -1,28 +1,35 @@ // Демо-данные (fallback, когда реальные источники не сконфигурированы). -// Транспорт «двигается» — формируются живые треки, как от реальных устройств. +// Сценарий: городские электросети — трансформаторные подстанции + бригадный автопарк. import { upsertEntity, addPosition, setChannels, updateChannel, addEvent, setEnergySeries, setTrips } from './store.js'; export function seedDemo() { - // Энерго-щит (стационар) - upsertEntity({ id: 'cab-07', clientId: 'tempelhoff', name: 'Щит КТП-7', type: 'energy', online: false, lat: 44.08, lon: 43.0, telemetry: { pwr_ext: 0, pwr_int: 3.9, temp: 24 } }); + // --- Трансформаторные подстанции / РП (узлы энергомониторинга, стационар) --- + const subs = [ + ['tp-14', 'ТП-14 «Бештау»', 44.082, 43.002, false, { load: 0, u: 0, i: 0, temp: 23 }, { feeder: 'ВЛ-10 «Север»' }], + ['tp-07', 'ТП-7 «Центр»', 44.048, 43.058, true, { load: 184, u: 402, i: 265, temp: 41 }, { feeder: 'ВЛ-10 «Центр»' }], + ['tp-03', 'ТП-3 «Западный»', 44.041, 43.02, true, { load: 96, u: 398, i: 139, temp: 33 }, { feeder: 'ВЛ-10 «Запад»' }], + ['tp-09', 'ТП-9 «Южный»', 44.018, 43.07, true, { load: 142, u: 401, i: 205, temp: 37 }, { feeder: 'ВЛ-10 «Юг»' }], + ['rp-02', 'РП-2 (распределительный пункт)', 44.062, 43.045, true, { load: 310, u: 6300, i: 29, temp: 29 }, { feeder: 'ПС 110/10 «Город»' }], + ]; + subs.forEach(([id, name, lat, lon, online, t, meta]) => upsertEntity({ id, clientId: 'tempelhoff', name, type: 'energy', online, lat, lon, telemetry: t, meta })); - // Сетевые узлы mesh2 + // --- Сетевые узлы mesh2 (раздел «Сеть») --- const nodes = [ ['mesh2-hub', 'finik-хаб', 44.072, 43.05, { latency: 1.2, uplink: 940, clients: 38, cpu: 17 }, { ip: '10.200.0.1', role: 'Центральный хаб mesh2' }], - ['mesh2-kn1', 'Keenetic · Склад', 44.064, 43.058, { latency: 3.4, uplink: 310, clients: 12, cpu: 22 }, { ip: '10.200.1.2', role: 'Спица · склад' }], - ['mesh2-kn2', 'Keenetic · Ферма', 44.081, 43.041, { latency: 5.1, uplink: 95, clients: 7, cpu: 19 }, { ip: '10.200.2.2', role: 'Спица · ферма' }], + ['mesh2-kn1', 'Keenetic · РЭС-1', 44.064, 43.058, { latency: 3.4, uplink: 310, clients: 12, cpu: 22 }, { ip: '10.200.1.2', role: 'Спица · РЭС-1' }], + ['mesh2-kn2', 'Keenetic · Диспетчерская', 44.081, 43.041, { latency: 5.1, uplink: 95, clients: 7, cpu: 19 }, { ip: '10.200.2.2', role: 'Спица · диспетчерская' }], ]; nodes.forEach(([id, name, lat, lon, t, meta]) => upsertEntity({ id, clientId: 'tempelhoff', name, type: 'network', online: true, lat, lon, telemetry: t, meta })); - // Каналы Wiren Board + // Каналы Wiren Board (телеметрия щита ТП) setChannels([ - { id: 'mr6c_214/K1', device: 'WB-MR6C #214', name: 'Освещение цех', kind: 'relay', on: true }, - { id: 'mr6c_214/K2', device: 'WB-MR6C #214', name: 'Ворота', kind: 'relay', on: false }, - { id: 'mdm3_30/CH1', device: 'WB-MDM3 #30', name: 'Свет офис', kind: 'dimmer', on: true, level: 65 }, - { id: 'mr6c_214/IN1', device: 'WB-MR6C #214', name: 'Датчик двери', kind: 'input', on: false }, + { id: 'mr6c_214/K1', device: 'WB-MR6C #214', name: 'Ввод №1 (секц. выкл.)', kind: 'relay', on: true }, + { id: 'mr6c_214/K2', device: 'WB-MR6C #214', name: 'Ввод №2 (резерв АВР)', kind: 'relay', on: false }, + { id: 'mdm3_30/CH1', device: 'WB-MDM3 #30', name: 'Освещение ТП', kind: 'dimmer', on: true, level: 65 }, + { id: 'mr6c_214/IN1', device: 'WB-MR6C #214', name: 'Дверь ТП (охрана)', kind: 'input', on: false }, ]); - // Энерго-ряд за сутки + // Энерго-ряд за сутки (суммарная нагрузка сети) setEnergySeries( Array.from({ length: 24 }, (_, h) => { const s = Math.max(0, Math.sin(((h - 6) / 12) * Math.PI)); @@ -30,47 +37,54 @@ export function seedDemo() { }), ); - // Поездки (журнал) + // Поездки бригад (журнал) setTrips([ - { id: 't1', entityId: 'veh-123', start: new Date(Date.now() - 6 * 3.6e6).toISOString(), end: new Date(Date.now() - 5.2 * 3.6e6).toISOString(), from: 'База', to: 'Пятигорск', distanceKm: 18.4, durationMin: 48, maxSpeed: 78, avgSpeed: 41 }, - { id: 't2', entityId: 'veh-123', start: new Date(Date.now() - 2 * 3.6e6).toISOString(), end: new Date(Date.now() - 1.4 * 3.6e6).toISOString(), from: 'Ессентуки', to: 'База', distanceKm: 31.6, durationMin: 37, maxSpeed: 91, avgSpeed: 51 }, + { id: 't1', entityId: 'veh-avar', start: new Date(Date.now() - 6 * 3.6e6).toISOString(), end: new Date(Date.now() - 5.2 * 3.6e6).toISOString(), from: 'РЭС-1', to: 'ТП-9 «Южный»', distanceKm: 18.4, durationMin: 48, maxSpeed: 78, avgSpeed: 41 }, + { id: 't2', entityId: 'veh-agp22', start: new Date(Date.now() - 2 * 3.6e6).toISOString(), end: new Date(Date.now() - 1.4 * 3.6e6).toISOString(), from: 'База', to: 'ул. Лесная (ЛЭП)', distanceKm: 12.6, durationMin: 34, maxSpeed: 61, avgSpeed: 33 }, ]); - addEvent({ id: 'e1', entityId: 'cab-07', callsign: 'Щит КТП-7', severity: 'critical', type: 'power_loss', text: 'Пропадание сетевого питания, переход на ИБП.', ts: new Date().toISOString(), ackRequired: true }); + // Стартовые события + addEvent({ id: 'e1', entityId: 'tp-14', callsign: 'ТП-14 «Бештау»', severity: 'critical', type: 'power_loss', text: 'Пропадание питания на ВЛ-10 «Север». Потребители обесточены, выехала ВЛ-1.', ts: new Date().toISOString(), ackRequired: true }); + addEvent({ id: 'e2', entityId: 'tp-07', callsign: 'ТП-7 «Центр»', severity: 'warning', type: 'overload', text: 'Перегрев трансформатора 41 °C при нагрузке 184 кВт.', ts: new Date().toISOString() }); - // Подвижные объекты + стартовые позиции + // --- Бригадный автопарк (5 машин, включая автовышку) — подвижные объекты --- const moving = { - 'veh-123': { name: 'ГАЗель-123', lat: 44.05, lon: 43.06, hd: 0.4 }, - 'veh-204': { name: 'МТЗ-82 трактор', lat: 44.02, lon: 43.11, hd: 1.2 }, + 'veh-agp22': { name: 'Автовышка АГП-22', lat: 44.046, lon: 43.062, hd: 0.4, drive: true, meta: { boom: 'поднята' } }, + 'veh-vl1': { name: 'Бригада ВЛ-1 (ГАЗель)', lat: 44.081, lon: 43.001, hd: 1.0, drive: false }, + 'veh-avar': { name: 'Аварийная бригада (УАЗ)', lat: 44.058, lon: 43.041, hd: 2.1, drive: true }, + 'veh-kabel': { name: 'Кабельщики (ГАЗ-3309)', lat: 44.033, lon: 43.089, hd: 3.4, drive: true }, + 'veh-master': { name: 'Мастер участка (Нива)', lat: 44.069, lon: 43.073, hd: 5.0, drive: true }, }; Object.entries(moving).forEach(([id, m]) => - upsertEntity({ id, clientId: 'tempelhoff', name: m.name, type: 'transport', online: true, lat: m.lat, lon: m.lon, telemetry: { speed: 0, sat: 10, fuel: 55, voltage: 13.6 } }), + upsertEntity({ id, clientId: 'tempelhoff', name: m.name, type: 'transport', online: true, lat: m.lat, lon: m.lon, telemetry: { speed: 0, sat: 11, fuel: 55, voltage: 13.7 }, meta: m.meta }), ); - // Тикер «движения» → живые треки (имитация приёма с устройств) + // Тикер «движения» → живые треки setInterval(() => { for (const [id, m] of Object.entries(moving)) { - m.hd += (Math.random() - 0.5) * 0.6; - const speed = id === 'veh-204' ? Math.round(Math.random() * 12) : Math.round(35 + Math.random() * 45); - const step = (speed / 3600) * 0.02; - m.lat += Math.cos(m.hd) * step; - m.lon += Math.sin(m.hd) * step; - addPosition(id, +m.lat.toFixed(5), +m.lon.toFixed(5), { speed, sat: 9 + Math.round(Math.random() * 4), fuel: 50, voltage: 13.6 }); + const speed = m.drive ? Math.round(20 + Math.random() * 45) : 0; + if (m.drive) { + m.hd += (Math.random() - 0.5) * 0.6; + const step = (speed / 3600) * 0.02; + m.lat += Math.cos(m.hd) * step; + m.lon += Math.sin(m.hd) * step; + } + addPosition(id, +m.lat.toFixed(5), +m.lon.toFixed(5), { speed, sat: 9 + Math.round(Math.random() * 4), fuel: 50, voltage: 13.7 }); } }, 3000); - // Датчик двери: периодическая смена статуса + событие — демонстрация live-обновления по WS + // Дверь ТП: периодическая смена статуса + событие (демо live-обновления по WS) let door = false; setInterval(() => { door = !door; updateChannel('mr6c_214/IN1', { on: door }); addEvent({ id: 'door-' + new Date().toISOString(), - entityId: 'cab-07', - callsign: 'Датчик двери · WB-MR6C #214', + entityId: 'tp-07', + callsign: 'Дверь ТП-7 · WB-MR6C #214', severity: door ? 'warning' : 'info', type: door ? 'door_open' : 'door_closed', - text: door ? 'Дверь шкафа открыта' : 'Дверь шкафа закрыта', + text: door ? 'Несанкц. доступ: дверь ТП-7 открыта' : 'Дверь ТП-7 закрыта', ts: new Date().toISOString(), }); }, 9000);