homebridge-enphase-envoy
Version:
Homebridge plugin for Photovoltaic Energy System manufactured by Enphase.
917 lines (830 loc) • 423 kB
JavaScript
import EventEmitter from 'events';
import { statfs, stat } from 'fs/promises';
import { dirname } from 'path';
import EnvoyData from './envoydata.js';
import Functions from './functions.js';
import fakegato from 'fakegato-history';
import { PartNumbers, ApiCodes, MetersKeyMap, MetersKeyMap1, DeviceTypeMap, LedStatus } from './constants.js';
const HISTORY_KEYS = ['ts', 'prt', 'pr', 'prit', 'prut', 'pru', 'cnt', 'cn', 'cnit', 'cnut', 'cnu', 'ctt', 'ct', 'ctit', 'ctpt', 'ctp'];
const toColumnar = (records) => {
const col = {};
for (const k of HISTORY_KEYS) col[k] = records.map(r => r?.[k] ?? null);
return col;
};
const fromColumnar = (data) => {
if (Array.isArray(data)) return data;
const keys = Object.keys(data);
const len = data[keys[0]]?.length ?? 0;
return Array.from({ length: len }, (_, i) => {
const obj = {};
for (const k of keys) obj[k] = data[k]?.[i] ?? null;
return obj;
});
};
let Accessory, Characteristic, Service, Categories, AccessoryUUID;
class EnvoyDevice extends EventEmitter {
constructor(api, log, url, deviceName, device, envoyIdFile, envoyTokenFile, prefDir, energyLifetimeHistoryFile, energyMeterHistoryFileName, restFul1 = null, restFulConnected = false, mqtt1 = null, mqttConnected = false) {
super();
Accessory = api.platformAccessory;
Characteristic = api.hap.Characteristic;
Service = api.hap.Service;
Categories = api.hap.Categories;
AccessoryUUID = api.hap.uuid;
//device configuration
this.log = log;
this.url = url;
this.device = device;
this.name = deviceName;
this.displayType = device.displayType;
this.energyMeter = device.energyMeter || false;
this.energyHistoryTime = device.energyHistoryTime || 0;
this.energyHistoryReserveSpace = device.energyHistoryReserveSpace || 1;
this.lockControl = device.lockControl?.enable || false;
this.lockControlPrefix = device.lockControl?.prefix || false;
this.lockControlTime = (device.lockControl?.time || 30) * 1000;
this.productionStateSensor = device.productionStateSensor || {};
this.plcLevelCheckControl = device.plcLevelControl || {};
this.powerProductionSummary = device.powerProductionSummary || 1;
this.powerProductionLevelSensors = (device.powerProductionLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyProductionLevelSensors = (device.energyProductionLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyProductionLifetimeOffset = device.energyProductionLifetimeOffset || 0;
this.powerConsumptionTotalLevelSensors = (device.powerConsumptionTotalLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyConsumptionTotalLevelSensors = (device.energyConsumptionTotalLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyConsumptionTotalLifetimeOffset = device.energyConsumptionTotalLifetimeOffset || 0;
this.powerConsumptionNetLevelSensors = (device.powerConsumptionNetLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyConsumptionNetLevelSensors = (device.energyConsumptionNetLevelSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.energyConsumptionNetLifetimeOffset = device.energyConsumptionNetLifetimeOffset || 0;
//grid
this.gridProductionQualitySensors = (device.gridProductionQualitySensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.gridConsumptionTotalQualitySensors = (device.gridConsumptionTotalQualitySensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
this.gridConsumptionNetQualitySensors = (device.gridConsumptionNetQualitySensors || []).filter(sensor => (sensor.displayType ?? 0) > 0);
//qRelay
this.qRelayStateSensor = device.qRelayStateSensor || {};
//ac battery
this.acBatterieName = device.acBatterieName || 'AC Battery';
this.acBatterieBackupLevelSummaryControl = device.acBatterieBackupLevelSummaryAccessory || {};
this.acBatterieBackupLevelControl = device.acBatterieBackupLevelAccessory || {};
//enpower
this.enpowerDryContactsControl = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enpowerDryContactsControl || false) : false;
this.enpowerDryContactsSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enpowerDryContactsSensor || false) : false;
this.enpowerGridStateControl = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enpowerGridStateControl || {}) : {};
this.enpowerGridStateSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enpowerGridStateSensor || {}) : {};
this.enpowerGridModeSensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enpowerGridModeSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
//encharge
this.enchargeName = device.enchargeName || 'Encharge';
this.enchargeBackupLevelSummaryControl = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeBackupLevelSummaryAccessory || {}) : {};
this.enchargeBackupLevelSummarySensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeBackupLevelSummarySensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
this.enchargeBackupLevelControl = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeBackupLevelAccessory || {}) : {};
this.enchargeStateSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeStateSensor || {}) : {};
this.enchargeProfileControls = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeProfileControls || []).filter(control => (control.displayType ?? 0) > 0) : [];
this.enchargeProfileSensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeProfileSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
this.enchargeGridStateSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeGridStateSensor || {}) : {};
this.enchargeGridModeSensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.enchargeGridModeSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
//solar
this.solarGridStateSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.solarGridStateSensor || {}) : {};
this.solarGridModeSensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.solarGridModeSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
//generator
this.generatorStateControl = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.generatorStateControl || {}) : {};
this.generatorStateSensor = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.generatorStateSensor || {}) : {};
this.generatorModeControls = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.generatorModeControls || []).filter(control => (control.displayType ?? 0) > 0) : [];
this.generatorModeSensors = device.envoyFirmware7xxTokenGenerationMode > 0 ? (device.generatorModeSensors || []).filter(sensor => (sensor.displayType ?? 0) > 0) : [];
//data refresh
this.dataSamplingControl = device.dataRefreshControl || {};
this.dataSamplingSensor = device.dataRefreshSensor || {};
//log
this.logInfo = device.log?.info ?? false;
this.logWarn = device.log?.warn ?? true;
this.logError = device.log?.error ?? true;
this.logDebug = device.log?.debug ?? false;
//external integrations
this.restFul = device.restFul ?? {};
this.restFul1 = restFul1;
this.restFulConnected = restFulConnected;
this.mqtt = device.mqtt ?? {};
this.mqtt1 = mqtt1;
this.mqttConnected = mqttConnected;
//system accessory
this.systemAccessory = {
serviceType: [null, Service.Lightbulb, Service.Fan, Service.HumiditySensor, Service.CarbonMonoxideSensor][device.displayType],
characteristicType: [null, Characteristic.On, Characteristic.On, Characteristic.StatusActive, Characteristic.CarbonMonoxideDetected][device.displayType],
characteristicType1: [null, Characteristic.Brightness, Characteristic.RotationSpeed, Characteristic.CurrentRelativeHumidity, Characteristic.CarbonMonoxideLevel][device.displayType],
state: false,
level: 0
}
//production state sensor
if (this.productionStateSensor.displayType > 0) {
const sensor = this.productionStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//plc level check control
if (this.plcLevelCheckControl.displayType > 0) {
const control = this.plcLevelCheckControl;
control.serviceType = [null, Service.Switch, Service.Outlet, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.On][control.displayType];
control.state = false;
}
//data sampling control
if (this.dataSamplingControl.displayType > 0) {
const control = this.dataSamplingControl;
control.serviceType = [null, Service.Switch, Service.Outlet, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.On][control.displayType];
control.state = false;
}
//data sampling sensor
if (this.dataSamplingSensor.displayType > 0) {
const sensor = this.dataSamplingSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//power production sensors
for (const sensor of this.powerProductionLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.energyProductionLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//power consumption total sensor
for (const sensor of this.powerConsumptionTotalLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.energyConsumptionTotalLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//power consumption net sensor
for (const sensor of this.powerConsumptionNetLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.energyConsumptionNetLevelSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//grid quality sensors
for (const sensor of this.gridProductionQualitySensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.gridConsumptionTotalQualitySensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.gridConsumptionNetQualitySensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//qRelay
if (this.qRelayStateSensor.displayType > 0) {
const sensor = this.qRelayStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state0 = false;
sensor.state1 = false;
sensor.state2 = false;
sensor.state3 = false;
}
//ac battery
if (this.acBatterieBackupLevelSummaryControl.displayType > 0) {
const control = this.acBatterieBackupLevelSummaryControl;
control.serviceType = [null, Service.Lightbulb, Service.Fan, Service.HumiditySensor, Service.CarbonMonoxideSensor, Service.Battery][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.StatusActive, Characteristic.CarbonMonoxideDetected, Characteristic.StatusLowBattery][control.displayType];
control.characteristicType1 = [null, Characteristic.Brightness, Characteristic.RotationSpeed, Characteristic.CurrentRelativeHumidity, Characteristic.CarbonMonoxideLevel, Characteristic.BatteryLevel][control.displayType];
control.state = false;
control.backupLevel = 0;
}
if (this.acBatterieBackupLevelControl.displayType > 0) {
const control = this.acBatterieBackupLevelControl;
control.serviceType = [null, Service.Battery][control.displayType];
control.characteristicType = [null, Characteristic.StatusLowBattery][control.displayType];
control.characteristicType1 = [null, Characteristic.BatteryLevel][control.displayType];
control.characteristicType2 = [null, Characteristic.ChargingState][control.displayType];
control.state = false;
control.backupLevel = 0;
control.chargeState = 0;
}
//enpower
if (this.enpowerGridStateControl.displayType > 0) {
const control = this.enpowerGridStateControl;
control.serviceType = [null, Service.Switch, Service.Outlet, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.On][control.displayType];
control.state = false;
}
if (this.enpowerGridStateSensor.displayType > 0) {
const sensor = this.enpowerGridStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.enpowerGridModeSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//encharge
if (this.enchargeBackupLevelSummaryControl.displayType > 0) {
const control = this.enchargeBackupLevelSummaryControl;
control.serviceType = [null, Service.Lightbulb, Service.Fan, Service.HumiditySensor, Service.CarbonMonoxideSensor, Service.Battery][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.StatusActive, Characteristic.CarbonMonoxideDetected, Characteristic.StatusLowBattery][control.displayType];
control.characteristicType1 = [null, Characteristic.Brightness, Characteristic.RotationSpeed, Characteristic.CurrentRelativeHumidity, Characteristic.CarbonMonoxideLevel, Characteristic.BatteryLevel][control.displayType];
control.state = false;
control.backupLevel = 0;
}
for (const sensor of this.enchargeBackupLevelSummarySensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
if (this.enchargeBackupLevelControl.displayType > 0) {
const control = this.enchargeBackupLevelControl;
control.serviceType = [null, Service.Battery][control.displayType];
control.characteristicType = [null, Characteristic.BatteryLevel][control.displayType];
control.characteristicType1 = [null, Characteristic.StatusLowBattery][control.displayType];
control.characteristicType2 = [null, Characteristic.ChargingState][control.displayType];
control.state = false;
control.backupLevel = 0;
control.chargeState = 0;
}
if (this.enchargeStateSensor.displayType > 0) {
const sensor = this.enchargeStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const control of this.enchargeProfileControls) {
control.serviceType = [null, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On][control.displayType];
control.state = false;
control.reservedSoc = 0;
control.previousState = null;
}
for (const sensor of this.enchargeProfileSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
if (this.enchargeGridStateSensor.displayType > 0) {
const sensor = this.enchargeGridStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.enchargeGridModeSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//solar
if (this.solarGridStateSensor.displayType > 0) {
const sensor = this.solarGridStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const sensor of this.solarGridModeSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//generator
if (this.generatorStateControl.displayType > 0) {
const control = this.generatorStateControl;
control.serviceType = [null, Service.Switch, Service.Outlet, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.On][control.displayType];
control.state = false;
}
if (this.generatorStateSensor.displayType > 0) {
const sensor = this.generatorStateSensor;
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
for (const control of this.generatorModeControls) {
control.serviceType = [null, Service.Switch, Service.Outlet, Service.Lightbulb][control.displayType];
control.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.On][control.displayType];
control.state = false;
control.previousState = null;
}
for (const sensor of this.generatorModeSensors) {
sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType];
sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType];
sensor.state = false;
}
//setup variables
this.functions = new Functions();
this.envoyIdFile = envoyIdFile;
this.envoyTokenFile = envoyTokenFile;
this.energyLifetimeHistoryFile = energyLifetimeHistoryFile;
this.savedHistory = false;
//fakegato
this.fakegatoHistory = fakegato(api);
this.prefDir = prefDir;
this.energyMeterHistoryFileName = energyMeterHistoryFileName;
this.lastReset = 0;
//supported functions
this.feature = {
productionState: {
supported: false
},
home: {
supported: false,
networkInterfaces: {
supported: false,
installed: false,
count: 0
},
wirelessConnections: {
supported: false,
installed: false,
count: 0
},
},
inventory: {
supported: false,
pcus: {
supported: false,
installed: false,
count: 0,
status: {
supported: false
},
detailedData: {
supported: false
},
},
nsrbs: {
supported: false,
installed: false,
count: 0,
detailedData: {
supported: false
},
},
acbs: {
supported: false,
installed: false,
count: 0
},
esubs: {
supported: false,
installed: false,
count: 0,
encharges: {
supported: false,
installed: false,
count: 0,
status: {
supported: false
},
settings: {
supported: false
},
power: {
supported: false
},
tariff: {
supported: false
},
},
enpowers: {
supported: false,
installed: false,
count: 0,
status: {
supported: false
},
dryContacts: {
supported: false,
installed: false,
count: 0,
settings: {
supported: false,
count: 0
}
},
},
collars: {
supported: false,
installed: false,
count: 0,
},
c6CombinerControllers: {
supported: false,
installed: false,
count: 0,
},
c6Rgms: {
supported: false,
installed: false,
count: 0,
},
status: {
supported: false
},
counters: {
supported: false
},
secctrl: {
supported: false
},
relay: {
supported: false
},
generator: {
supported: false,
installed: false,
count: 0,
settings: {
supported: false
}
},
},
},
meters: {
supported: false,
installed: false,
count: 0,
production: {
supported: false,
enabled: false
},
consumptionNet: {
supported: false,
enabled: false
},
consumptionTotal: {
supported: false,
enabled: false
},
storage: {
supported: false,
enabled: false
},
backfeed: {
supported: false,
enabled: false
},
load: {
supported: false,
enabled: false
},
evse: {
supported: false,
enabled: false
},
pv3p: {
supported: false,
enabled: false
},
generator: {
supported: false,
enabled: false
},
detailedData: {
supported: false
},
},
productionCt: {
supported: false,
production: {
supported: false,
pcu: {
supported: false
},
eim: {
supported: false
}
},
consumptionNet: {
supported: false
},
consumptionTotal: {
supported: false
},
storage: {
supported: false
}
},
plcLevel: {
supported: false,
pcus: {
supported: false
},
nsrbs: {
supported: false
},
acbs: {
supported: false
},
},
powerAndEnergy: {
supported: false
},
liveData: {
supported: false
},
gridProfile: {
supported: false
}
};
//pv object
this.pv = {
info: {},
homeData: {},
inventoryData: {
pcus: [],
nsrbs: [],
acbs: [],
esubs: {
devices: [],
encharges: {
devices: [],
settings: {},
tariff: {},
ratedPowerSumKw: null,
realPowerSumKw: null,
phaseA: false,
phaseB: false,
phaseC: false,
},
enpowers: [],
collars: [],
c6CombinerControllers: [],
c6Rgms: [],
counters: {},
secctrl: {},
relay: {},
generator: {}
},
},
metersData: [],
powerAndEnergyData: {
data: [],
production: {
powerPeak: null,
energyToday: null,
energyTodayUpload: null,
energyLifetime: null,
energyLifetimeUpload: null
},
consumptionNet: {
powerPeak: null,
energyToday: null,
energyTodayUpload: null,
energyLifetime: null,
energyLifetimeUpload: null
},
consumptionTotal: {
powerPeak: null,
energyToday: null,
energyTodayFromPv: null,
energyLifetime: null,
energyLifetimeFromPv: null
},
},
liveData: {},
productionState: false,
plcLevelCheck: false,
dataSampling: false
};
}
async setOverExternalIntegration(integration, key, value) {
try {
switch (key) {
case 'DataSampling':
if (value !== this.dataSampling) await this.envoyData.startStopImpulseGenerator(value);
break;
case 'ProductionState':
if (this.feature.productionState.supported) await this.envoyData.setProductionState(value);
break;
case 'PlcLevel':
if (this.feature.plcLevel.supported) await this.envoyData.updatePlcLevel(false);
break;
case 'EnchargeProfile':
if (this.feature.inventory.esubs.encharges.tariff.supported) await this.envoyData.setEnchargeProfile(value, this.pv.inventoryData.esubs.encharges.tariff.storageSettings.reservedSoc, this.pv.inventoryData.esubs.encharges.tariff.storageSettings.chargeFromGrid);
break;
case 'EnchargeReservedSoc':
if (this.feature.inventory.esubs.encharges.tariff.supported) await this.envoyData.setEnchargeProfile(this.pv.inventoryData.esubs.encharges.tariff.storageSettings.mode, value, this.pv.inventoryData.esubs.encharges.tariff.storageSettings.chargeFromGrid);
break;
case 'EnchargeChargeFromGrid':
if (this.feature.inventory.esubs.encharges.tariff.supported) await this.envoyData.setEnchargeProfile(this.pv.inventoryData.esubs.encharges.tariff.storageSettings.mode, this.pv.inventoryData.esubs.encharges.tariff.storageSettings.reservedSoc, value);
break;
case 'EnpowerGridState':
if (this.feature.inventory.esubs.enpowers.installed) await this.envoyData.setEnpowerGridState(value);
break;
case 'GeneratorMode':
if (this.feature.inventory.esubs.generator.installed) await this.envoyData.setGeneratorMode(value);
break;
default:
if (this.logWarn) this.emit('warn', `${integration}, received key: ${key}, value: ${value}`);
break;
}
return;
} catch (error) {
throw new Error(`${integration} set key: ${key}, value: ${value}, error: ${error}`);
}
}
async startStopImpulseGenerator(state) {
try {
//start impulse generator
await this.envoyData.startStopImpulseGenerator(state);
return true;
} catch (error) {
throw new Error(`Impulse generator start error: ${error}`);
}
}
// Prepare accessory
async prepareAccessory() {
try {
//supported feature
let pvControl = true;
const productionStateSupported = this.feature.productionState.supported;
const gridProfileSupported = this.feature.gridProfile.supported;
const plcLevelSupported = this.feature.plcLevel.supported;
const plcLevelPcusSupported = this.feature.plcLevel.pcus.supported;
const plcLevelNrsbsSupported = this.feature.plcLevel.nsrbs.supported;
const plcLevelAcbsSupported = this.feature.plcLevel.acbs.supported;
const envoySupported = this.feature.home.supported;
const networkInterfacesInstalled = this.feature.home.networkInterfaces.installed;
const wirelessConnectionsInstalled = this.feature.home.wirelessConnections.installed;
const metersInstalled = this.feature.meters.installed;
const pcuInstalled = this.feature.inventory.pcus.installed;
const pcusStatusDataSupported = this.feature.inventory.pcus.status.supported;
const pcusDetailedDataSupported = this.feature.inventory.pcus.detailedData.supported;
const nsrbsInstalled = this.feature.inventory.nsrbs.installed;
const nsrbsDetailedDataSupported = this.feature.inventory.nsrbs.detailedData.supported;
const acBatterieName = this.acBatterieName;
const acbsInstalled = this.feature.inventory.acbs.installed;
const acbsSupported = this.feature.productionCt.storage.supported;
const powerAndEnergySupported = this.feature.powerAndEnergy.supported;
const ensemblesSupported = this.feature.inventory.esubs.supported;
const ensemblesInstalled = this.feature.inventory.esubs.installed;
const ensemblesStatusSupported = this.feature.inventory.esubs.status.supported;
const ensemblesSecCtrlSupported = this.feature.inventory.esubs.secctrl.supported;
const ensemblesCountersSupported = this.feature.inventory.esubs.counters.supported;
const ensemblesRelaySupported = this.feature.inventory.esubs.relay.supported;
const enchargeName = this.enchargeName;
const enchargesInstalled = this.feature.inventory.esubs.encharges.installed;
const enchargesStatusSupported = this.feature.inventory.esubs.encharges.status.supported;
const enchargesPowerSupported = this.feature.inventory.esubs.encharges.power.supported;
const enchargesSettingsSupported = this.feature.inventory.esubs.encharges.settings.supported;
const enchargesTariffSupported = this.feature.inventory.esubs.encharges.tariff.supported;
const enpowersInstalled = this.feature.inventory.esubs.enpowers.installed;
const enpowersStatusSupported = this.feature.inventory.esubs.enpowers.status.supported;
const enpowersDryContactsInstalled = this.feature.inventory.esubs.enpowers.dryContacts.installed;
const collarsInstalled = this.feature.inventory.esubs.collars.installed;
const c6CombinerControllersInstalled = this.feature.inventory.esubs.c6CombinerControllers.installed;
const c6RgmsInstalled = this.feature.inventory.esubs.c6Rgms.installed;
const generatorInstalled = this.feature.inventory.esubs.generator.installed;
const liveDataSupported = this.feature.liveData.supported;
const accessories = [];
const devices = this.energyMeter ? 2 : 1;
for (let i = 0; i < devices; i++) {
switch (i) {
case 0: //PV System
const envoySerialNumber = this.pv.info.serialNumber;
const accessoryName = this.name;
//accessory
if (this.logDebug) this.emit('debug', `Prepare ${accessoryName} accessory`);
const accessoryUUID = AccessoryUUID.generate(envoySerialNumber);
const accessoryCategory = [Categories.OTHER, Categories.LIGHTBULB, Categories.FAN, Categories.SENSOR, Categories.SENSOR][this.displayType];
const accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory);
//information service
if (this.logDebug) this.emit('debug', `Prepare Information Service`);
accessory.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, 'Enphase')
.setCharacteristic(Characteristic.Model, this.pv.info.modelName ?? 'Model Name')
.setCharacteristic(Characteristic.SerialNumber, envoySerialNumber ?? 'Serial Number')
.setCharacteristic(Characteristic.FirmwareRevision, this.pv.info.software.replace(/[a-zA-Z]/g, '') ?? '0');
//system
if (this.systemAccessory) {
if (this.logDebug) this.emit('debug', `Prepare System Service`);
const systemAccessory = this.systemAccessory;
const { serviceType, characteristicType, characteristicType1 } = systemAccessory;
const systemService = accessory.addService(serviceType, accessoryName, `systemService`);
systemService.setPrimaryService(true);
systemService.addOptionalCharacteristic(Characteristic.ConfiguredName);
systemService.setCharacteristic(Characteristic.ConfiguredName, accessoryName);
// Handle production state characteristic
systemService.getCharacteristic(characteristicType)
.onGet(async () => {
const currentState = systemAccessory.state;
if (this.logInfo) this.emit('info', `Production state: ${currentState ? 'Enabled' : 'Disabled'}`);
return currentState;
})
.onSet(async (value) => {
if (!productionStateSupported || !pvControl) {
if (this.logWarn) this.emit('warn', !productionStateSupported ? `Production state control not supported` : `System control is locked`);
setTimeout(() => systemService.updateCharacteristic(characteristicType, !value), 250);
return;
}
try {
const tokenValid = await this.checkToken();
if (!tokenValid || value === this.pv.productionState) {
setTimeout(() => systemService.updateCharacteristic(characteristicType, !value), 250);
return;
}
await this.envoyData.setProductionState(value);
if (this.logDebug) this.emit('debug', `Set production state: ${value ? 'Enabled' : 'Disabled'}`);
} catch (error) {
if (this.logWarn) this.emit('warn', `Set production state error: ${error}`);
}
});
// Handle production level characteristic
systemService.getCharacteristic(characteristicType1)
.onGet(async () => {
const powerLevel = systemAccessory.level;
if (this.logInfo) this.emit('info', `Production level: ${powerLevel} %`);
return powerLevel;
});
this.systemService = systemService;
}
//data refresh control
if (this.dataSamplingControl.displayType > 0) {
if (this.logDebug) this.emit('debug', `Prepare Data Refresh Control Service`);
const control = this.dataSamplingControl;
const { name, namePrefix, serviceType, characteristicType } = control;
const serviceName = namePrefix ? `${accessoryName} ${name}` : name;
const controlService = accessory.addService(serviceType, serviceName, `dataSamplingControlService`);
controlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
controlService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
controlService.getCharacteristic(characteristicType)
.onGet(async () => {
const currentState = control.state;
if (this.logInfo) this.emit('info', `Data refresh control: ${currentState ? 'Enabled' : 'Disabled'}`);
return currentState;
})
// SET handler
.onSet(async (value) => {
if (!pvControl) {
if (this.logWarn) this.emit('warn', `System control is locked`);
setTimeout(() => controlService.updateCharacteristic(characteristicType, !value), 250);
return;
}
try {
await this.envoyData.startStopImpulseGenerator(value);
if (this.logInfo) this.emit('info', `Set data refresh control to: ${value ? 'Enable' : 'Disable'}`);
} catch (error) {
if (this.logWarn) this.emit('warn', `Set data refresh control error: ${error}`);
}
});
this.dataSamplingControlService = controlService;
}
//data refresh sensor
if (this.dataSamplingSensor.displayType > 0) {
if (this.logDebug) this.emit('debug', `Prepare Data Refresh Sensor Service`);
const sensor = this.dataSamplingSensor;
const { name, namePrefix, serviceType, characteristicType } = sensor;
const serviceName = namePrefix ? `${accessoryName} ${name}` : name;
const sensorService = accessory.addService(serviceType, serviceName, `dataSamplingSensorService`);
sensorService.addOptionalCharacteristic(Characteristic.ConfiguredName);
sensorService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
sensorService.getCharacteristic(characteristicType)
.onGet(async () => {
const currentState = sensor.state;
if (this.logInfo) this.emit('info', `Data refresh sensor: ${currentState ? 'Active' : 'Not active'}`);
return currentState;
});
this.dataSamplingSensorService = sensorService;
}
//production state sensor
if (this.productionStateSensor.displayType > 0 && productionStateSupported) {
if (this.logDebug) this.emit('debug', `Prepare Production State Sensor Service`);
const sensor = this.productionStateSensor;
const { name, namePrefix, serviceType, characteristicType } = sensor;
const serviceName = namePrefix ? `${accessoryName} ${name}` : name;
const sensorService = accessory.addService(serviceType, serviceName, `productionStateSensorService`);
sensorService.addOptionalCharacteristic(Characteristic.ConfiguredName);
sensorService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
sensorService.getCharacteristic(characteristicType)
.onGet(async () => {
const currentState = sensor.state;
if (this.logInfo) this.emit('info', `Production state sensor: ${currentState ? 'Active' : 'Not active'}`);
return currentState;
});
this.productionStateSensorService = sensorService;
}
//plc level control
if (this.plcLevelCheckControl.displayType > 0 && plcLevelSupported) {
if (this.logDebug) this.emit('debug', `Prepare Plc Level Control Service`);
const sensor = this.plcLevelCheckControl;
const { name, namePrefix, serviceType, characteristicType } = sensor;
const serviceName = namePrefix ? `${accessoryName} ${name}` : name;
const controlService = accessory.addService(serviceType, serviceName, `plcLevelCheckControlService`);
controlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
controlService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
controlService.getCharacteristic(characteri