UNPKG

homebridge-enphase-envoy

Version:

Homebridge plugin for Photovoltaic Energy System manufactured by Enphase.

917 lines (830 loc) • 423 kB
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