Files
2026-05-26 19:42:20 +00:00

361 lines
10 KiB
JavaScript

#!/usr/bin/env node
/**
* Data Seeding Script for ModPulse
* Populates the system with realistic PLC/SCADA data
* Scenario: Food processing plant
*/
const BASE_URL = process.env.BASE_URL || 'http://localhost:5000';
const API_KEY = process.env.MODPULSE_API_KEY || 'dev-api-key-for-local-testing';
async function api(path, method = 'GET', body = null) {
const url = `${BASE_URL}${path}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY
}
};
if (body) options.body = JSON.stringify(body);
const res = await fetch(url, options);
if (!res.ok) {
const text = await res.text();
throw new Error(`API ${method} ${path} failed: ${res.status} ${text}`);
}
return res.json();
}
async function seed() {
console.log('🌱 Seeding ModPulse with realistic industrial data...\n');
// 1. CREATE MASTERS (Modbus connections)
console.log('📡 Creating Modbus Masters...');
const masters = [
{
name: 'Line-A-Main-PLC',
description: 'Production Line A - Allen-Bradley ControlLogix controlling mixing and packaging',
type: 'TCP',
connectionString: '192.168.10.10:502',
pollIntervalMs: 1000,
timeoutMs: 5000,
isActive: true
},
{
name: 'Line-B-Main-PLC',
description: 'Production Line B - Siemens S7-1500 controlling filling and capping',
type: 'TCP',
connectionString: '192.168.10.20:502',
pollIntervalMs: 1000,
timeoutMs: 5000,
isActive: true
},
{
name: 'Cold-Storage-Gateway',
description: 'Moxa MGate MB3170 - 8 temperature sensors in cold storage',
type: 'TCP',
connectionString: '192.168.10.50:502',
pollIntervalMs: 2000,
timeoutMs: 3000,
isActive: true
},
{
name: 'Boiler-Control-PLC',
description: 'Honeywell MasterLogic - Steam boiler pressure/temperature control',
type: 'TCP',
connectionString: '192.168.10.100:502',
pollIntervalMs: 500,
timeoutMs: 2000,
isActive: true
}
];
const createdMasters = [];
for (const m of masters) {
const result = await api('/api/masters', 'POST', m);
createdMasters.push(result);
console.log(` ✓ Master: ${m.name} (ID: ${result.id})`);
}
// 2. CREATE SLAVES (Field Devices)
console.log('\n🔌 Creating Field Devices (Slaves)...');
const slaves = [
// Line A devices
{ masterId: createdMasters[0].id, slaveId: 1, label: 'Mixing-Tank-VFD-01', isActive: true },
{ masterId: createdMasters[0].id, slaveId: 2, label: 'Transfer-Pump-01', isActive: true },
// Line B devices
{ masterId: createdMasters[1].id, slaveId: 10, label: 'Filler-Machine-01', isActive: true },
{ masterId: createdMasters[1].id, slaveId: 11, label: 'Capper-Machine-01', isActive: true },
// Cold storage devices
{ masterId: createdMasters[2].id, slaveId: 1, label: 'Cold-Room-01-Temp', isActive: true },
{ masterId: createdMasters[2].id, slaveId: 2, label: 'Cold-Room-02-Temp', isActive: true },
// Boiler devices
{ masterId: createdMasters[3].id, slaveId: 1, label: 'Steam-Pressure-PT101', isActive: true },
{ masterId: createdMasters[3].id, slaveId: 2, label: 'Steam-Temp-TT101', isActive: true },
{ masterId: createdMasters[3].id, slaveId: 3, label: 'Boiler-High-Level-LSH101', isActive: true }
];
const createdSlaves = [];
for (const s of slaves) {
const result = await api('/api/slaves', 'POST', s);
createdSlaves.push(result);
console.log(` ✓ Slave: ${s.label} (ID: ${result.id})`);
}
// 3. CREATE REGISTER MAPS (Data points)
console.log('\n📊 Creating Register Maps...');
const registerMaps = [
// VFD - Analog values
{
slaveId: createdSlaves[0].id,
name: 'VFD-01-Speed-Feedback',
description: 'Motor speed from VFD encoder',
functionCode: 3, // Holding Registers
startAddress: 40001,
registerCount: 1,
dataType: 5, // Float32
scaleFactor: 0.1,
unit: 'RPM',
logDeadband: 1.0,
maxLogIntervalSec: 60,
isActive: true
},
{
slaveId: createdSlaves[0].id,
name: 'VFD-01-Motor-Current',
description: 'Motor current for overload monitoring',
functionCode: 3,
startAddress: 40002,
registerCount: 1,
dataType: 1, // UInt16
scaleFactor: 0.01,
unit: 'A',
logDeadband: 0.5,
maxLogIntervalSec: 30,
isActive: true
},
// Cold storage - Temperature
{
slaveId: createdSlaves[4].id,
name: 'Cold-Room-01-Temp',
description: 'Cold storage room 1 temperature',
functionCode: 4, // Input Registers
startAddress: 30001,
registerCount: 1,
dataType: 2, // Int16
scaleFactor: 0.01,
unit: '°C',
logDeadband: 0.1,
maxLogIntervalSec: 10,
isActive: true
},
{
slaveId: createdSlaves[5].id,
name: 'Cold-Room-02-Temp',
description: 'Cold storage room 2 temperature',
functionCode: 4,
startAddress: 30001,
registerCount: 1,
dataType: 2,
scaleFactor: 0.01,
unit: '°C',
logDeadband: 0.1,
maxLogIntervalSec: 10,
isActive: true
},
// Boiler - Steam pressure
{
slaveId: createdSlaves[6].id,
name: 'Steam-Pressure-PT101',
description: 'Steam header pressure',
functionCode: 4,
startAddress: 30001,
registerCount: 1,
dataType: 1, // UInt16
scaleFactor: 0.001,
unit: 'psi',
logDeadband: 0.5,
maxLogIntervalSec: 5,
isActive: true
},
// Boiler - Steam temperature
{
slaveId: createdSlaves[7].id,
name: 'Steam-Temp-TT101',
description: 'Superheated steam temperature',
functionCode: 4,
startAddress: 30001,
registerCount: 1,
dataType: 1,
scaleFactor: 0.1,
unit: '°C',
logDeadband: 0.5,
maxLogIntervalSec: 5,
isActive: true
},
// Production counter
{
slaveId: createdSlaves[2].id,
name: 'Filler-Bottle-Count',
description: 'Bottles filled since last reset',
functionCode: 3,
startAddress: 40010,
registerCount: 1,
dataType: 1,
scaleFactor: 1,
unit: 'bottles',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
},
// Digital values - Coils
{
slaveId: createdSlaves[0].id,
name: 'VFD-01-Running',
description: 'Motor running status',
functionCode: 1, // Coils
startAddress: 1,
registerCount: 1,
dataType: 0, // Bool
scaleFactor: 1,
unit: '',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
},
{
slaveId: createdSlaves[0].id,
name: 'VFD-01-Fault',
description: 'VFD fault/alarm status',
functionCode: 1,
startAddress: 2,
registerCount: 1,
dataType: 0,
scaleFactor: 1,
unit: '',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
},
{
slaveId: createdSlaves[8].id,
name: 'Boiler-High-Level',
description: 'High level safety interlock',
functionCode: 2, // Discrete Inputs
startAddress: 10001,
registerCount: 1,
dataType: 0,
scaleFactor: 1,
unit: '',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
},
{
slaveId: createdSlaves[2].id,
name: 'Filler-Running',
description: 'Filler machine running',
functionCode: 1,
startAddress: 100,
registerCount: 1,
dataType: 0,
scaleFactor: 1,
unit: '',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
},
{
slaveId: createdSlaves[3].id,
name: 'Capper-Torque-OK',
description: 'Cap torque within spec',
functionCode: 1,
startAddress: 200,
registerCount: 1,
dataType: 0,
scaleFactor: 1,
unit: '',
logDeadband: 0,
maxLogIntervalSec: 0,
isActive: true
}
];
const createdMaps = [];
for (const m of registerMaps) {
const result = await api('/api/registermaps', 'POST', m);
createdMaps.push(result);
console.log(` ✓ Register: ${m.name} (ID: ${result.id})`);
}
// 4. CREATE RULES (Pulse Rules)
console.log('\n⚡ Creating Pulse Rules...');
const rules = [
// Conditional rules (alarms)
{
name: 'Cold-Room-Hi-Temp-Alarm',
slaveId: createdSlaves[4].id,
triggerType: 2, // Conditional
registerMapId: createdMaps[2].id, // Cold-Room-01-Temp
conditionExpression: 'value > 4.0',
targetAction: 'WriteSingleCoil',
targetParameters: '{"address":100,"value":1}',
isActive: true
},
{
name: 'Steam-Pressure-Hi-Alarm',
slaveId: createdSlaves[6].id,
triggerType: 2,
registerMapId: createdMaps[4].id, // Steam-Pressure
conditionExpression: 'value > 140',
targetAction: 'WriteSingleCoil',
targetParameters: '{"address":101,"value":1}',
isActive: true
},
{
name: 'VFD-Overload-Warning',
slaveId: createdSlaves[0].id,
triggerType: 2,
registerMapId: createdMaps[1].id, // VFD-Current
conditionExpression: 'value > 45',
targetAction: 'WriteSingleRegister',
targetParameters: '{"address":500,"value":1}',
isActive: true
},
// Scheduled maintenance reminder
{
name: 'Daily-Status-Report',
slaveId: createdSlaves[0].id,
triggerType: 1, // Cron
cronExpression: '0 6 * * *', // 6 AM daily
targetAction: 'WriteSingleRegister',
targetParameters: '{"address":999,"value":1}',
isActive: true
}
];
const createdRules = [];
for (const r of rules) {
const result = await api('/api/rules', 'POST', r);
createdRules.push(result);
console.log(` ✓ Rule: ${r.name} (ID: ${result.id})`);
}
// Summary
console.log('\n✅ Data seeding complete!');
console.log(` ${createdMasters.length} Masters`);
console.log(` ${createdSlaves.length} Devices (Slaves)`);
console.log(` ${createdMaps.length} Register Maps`);
console.log(` ${createdRules.length} Pulse Rules`);
}
seed().catch(err => {
console.error('❌ Seeding failed:', err.message);
process.exit(1);
});