140 lines
3.6 KiB
JavaScript
140 lines
3.6 KiB
JavaScript
// Minimal Open-EnergyMesh scaffold (offline-first MVP)
|
|
// Provides a tiny in-memory data model: Device, Inverter, Meter, DER, PriceQuote, Forecast
|
|
// and a lightweight EnergyMesh orchestrator with a simple energy-flow calculation.
|
|
|
|
class Device {
|
|
constructor(id, name, type) {
|
|
this.id = id;
|
|
this.name = name;
|
|
this.type = type; // e.g., 'inverter', 'meter', 'storage', 'der'
|
|
}
|
|
}
|
|
|
|
class Inverter {
|
|
constructor(id, deviceId, capacityW, outputW) {
|
|
this.id = id;
|
|
this.deviceId = deviceId;
|
|
this.capacityW = capacityW;
|
|
this.outputW = typeof outputW === 'number' ? outputW : 0; // current output in Watts
|
|
}
|
|
}
|
|
|
|
class Meter {
|
|
constructor(id, deviceId, consumptionW) {
|
|
this.id = id;
|
|
this.deviceId = deviceId;
|
|
this.consumptionW = typeof consumptionW === 'number' ? consumptionW : 0; // current consumption in Watts
|
|
}
|
|
}
|
|
|
|
class DER {
|
|
constructor(id, type, capacityW) {
|
|
this.id = id;
|
|
this.type = type; // 'inverter' | 'storage'
|
|
this.capacityW = capacityW;
|
|
}
|
|
}
|
|
|
|
class PriceQuote {
|
|
constructor(timestamp, pricePerKWh) {
|
|
this.timestamp = timestamp; // epoch ms
|
|
this.pricePerKWh = pricePerKWh; // number
|
|
}
|
|
}
|
|
|
|
class Forecast {
|
|
constructor(horizonHours, value) {
|
|
this.horizonHours = horizonHours;
|
|
this.value = value; // simple numeric forecast placeholder
|
|
}
|
|
}
|
|
|
|
class EnergyMesh {
|
|
constructor() {
|
|
this.devices = new Map(); // id -> Device
|
|
this.inverters = new Map(); // id -> Inverter
|
|
this.meters = new Map(); // id -> Meter
|
|
this.ders = new Map(); // id -> DER
|
|
this.quotes = [];
|
|
this.forecasts = [];
|
|
}
|
|
|
|
// Device helpers
|
|
addDevice(device) {
|
|
if (!(device instanceof Device)) {
|
|
throw new Error('addDevice expects a Device instance');
|
|
}
|
|
this.devices.set(device.id, device);
|
|
}
|
|
|
|
addInverter(inverter) {
|
|
if (!(inverter instanceof Inverter)) {
|
|
throw new Error('addInverter expects an Inverter instance');
|
|
}
|
|
this.inverters.set(inverter.id, inverter);
|
|
// implicitly ensure a device exists for this inverter
|
|
if (!this.devices.has(inverter.deviceId)) {
|
|
this.addDevice(new Device(inverter.deviceId, `Inverter-${inverter.deviceId}`, 'inverter'));
|
|
}
|
|
}
|
|
|
|
addMeter(meter) {
|
|
if (!(meter instanceof Meter)) {
|
|
throw new Error('addMeter expects a Meter instance');
|
|
}
|
|
this.meters.set(meter.id, meter);
|
|
if (!this.devices.has(meter.deviceId)) {
|
|
this.addDevice(new Device(meter.deviceId, `Meter-${meter.deviceId}`, 'meter'));
|
|
}
|
|
}
|
|
|
|
addDER(der) {
|
|
if (!(der instanceof DER)) {
|
|
throw new Error('addDER expects a DER instance');
|
|
}
|
|
this.ders.set(der.id, der);
|
|
}
|
|
|
|
addPriceQuote(quote) {
|
|
if (!(quote instanceof PriceQuote)) {
|
|
throw new Error('addPriceQuote expects a PriceQuote');
|
|
}
|
|
this.quotes.push(quote);
|
|
}
|
|
|
|
addForecast(forecast) {
|
|
if (!(forecast instanceof Forecast)) {
|
|
throw new Error('addForecast expects a Forecast');
|
|
}
|
|
this.forecasts.push(forecast);
|
|
}
|
|
|
|
// Simple energy-flow calculation: total generation minus total consumption
|
|
// Assumes inverter.outputW is generation and meter.consumptionW is load
|
|
computeFlow() {
|
|
let generation = 0;
|
|
for (const inv of this.inverters.values()) {
|
|
generation += typeof inv.outputW === 'number' ? inv.outputW : 0;
|
|
}
|
|
let consumption = 0;
|
|
for (const m of this.meters.values()) {
|
|
consumption += typeof m.consumptionW === 'number' ? m.consumptionW : 0;
|
|
}
|
|
return {
|
|
generationW: generation,
|
|
consumptionW: consumption,
|
|
netFlowW: generation - consumption
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
EnergyMesh,
|
|
Device,
|
|
Inverter,
|
|
Meter,
|
|
DER,
|
|
PriceQuote,
|
|
Forecast
|
|
};
|