homebridge-solaredge-inverter
Version:
A Homebridge plugin to pull SolarEdge solar inverter data into HomeKit as light sensors.
384 lines (349 loc) • 11.8 kB
JavaScript
const axios = require('axios');
const setupCache = require('axios-cache-adapter').setupCache;
var Service, Characteristic;
const DEF_MIN_LUX = 0,
DEF_MAX_LUX = 65535;
const DISPLAY_USAGE_SENSORS = 0;
const PLUGIN_NAME = 'homebridge-solaredge-inverter';
const ACCESSORY_NAME = 'SolarEdge Inverter';
module.exports = function(homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory(PLUGIN_NAME, ACCESSORY_NAME, SolarEdgeInverter);
}
/**
* Setup Cache For Axios to prevent additional requests
*/
const cache = setupCache({
maxAge: 1 * 60 * 1000, //in ms
readHeaders: false,
// For this example to work we disable query exclusion
exclude: { query: false }
})
const api = axios.create({
adapter: cache.adapter
})
function isEmptyObject(obj) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
/**
* Main API request with site overview data
*
* @param {siteID} the SolarEdge Site ID to be queried
* @param {apiKey} the SolarEdge monitoring API Key for access to the Site
*/
const getInverterData = async(siteID, apiKey, update_interval) => {
try {
return await api.get('https://monitoringapi.solaredge.com/site/'+siteID+'/overview?api_key='+apiKey, {
cache: {
maxAge: update_interval
}
})
} catch (error) {
console.error(error)
}
}
/**
* Gets and returns the accessory's value in the correct format.
*
* @param {siteID} the SolarEdge Site ID to be queried
* @param {apiKey} the SolarEdge monitoring API Key for access to the Site
* @param (log) access to the homebridge logfile
* @return {bool} the value for the accessory
*/
const getAccessoryValue = async(that) => {
if (that.debug) {
that.log.info('Calling API');
}
// To Do: Need to handle if no connection
const inverterData = await getInverterData(that.siteID, that.apiKey, that.update_interval)
if(inverterData) {
if (that.debug) {
if (inverterData.request.fromCache !== true) {
that.log.info('Data from API', inverterData.data.overview);
} else {
that.log.info('Data from cache', inverterData.data.overview);
}
}
if(inverterData.data.overview) {
return inverterData.data.overview;
}
else {
return null
}
} else {
return null
}
}
/**
* API request with power flow data
*
* @param {siteID} the SolarEdge Site ID to be queried
* @param {apiKey} the SolarEdge monitoring API Key for access to the Site
*/
const getPowerFlowData = async(siteID, apiKey, update_interval) => {
try {
return await api.get('https://monitoringapi.solaredge.com/site/'+siteID+'/currentPowerFlow?api_key='+apiKey, {
cache: {
maxAge: update_interval
}
})
} catch (error) {
console.error(error)
}
}
/**
* Gets and returns the battery's charge value in the correct format.
*/
const getBatteryValues = async (that) => {
if (that.debug) {
that.log.info('Calling Flow API');
}
// To Do: Need to handle if no connection
const powerFlowData = await getPowerFlowData(that.siteID, that.apiKey, that.update_interval)
if(powerFlowData) {
if (that.debug) {
if (powerFlowData.request.fromCache !== true) {
that.log.info('Data from Power Flow API', powerFlowData.data.siteCurrentPowerFlow);
} else {
that.log.info('Data from Power Flow cache', powerFlowData.data.siteCurrentPowerFlow);
}
}
if(powerFlowData.data.siteCurrentPowerFlow) {
return powerFlowData.data.siteCurrentPowerFlow;
}
else {
return null
}
} else {
return null
}
}
const update = async(that) => {
if(that.currentPower || that.lastDayPower || that.lastMonth || that.lastYear || that.lifeTime) {
const accessoryValue = await getAccessoryValue(that);
let power = 0;
if (accessoryValue) {
if(that.currentPower) {
if(parseFloat(accessoryValue.currentPower.power) > 0) {
if(that.currentWatts) {
power = Math.abs(Math.round((accessoryValue.currentPower.power + Number.EPSILON) *10) /10)
} else {
power = Math.abs(Math.round(((accessoryValue.currentPower.power / 1000) + Number.EPSILON) *10) /10)
}
}
that.currentPower
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(that.lastDayPower) {
power = Math.abs(Math.round(((accessoryValue.lastDayData.energy / 1000) + Number.EPSILON) *10) /10)
that.lastDayPower
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(that.lastMonth) {
power = Math.abs(Math.round(((accessoryValue.lastMonthData.energy / 1000) + Number.EPSILON) *10) /10)
that.lastMonth
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(that.lastYear) {
power = Math.abs(Math.round(((accessoryValue.lastYearData.energy / 1000) + Number.EPSILON) *10) /10)
that.lastYear
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(that.lifeTime) {
power = Math.abs(Math.round(((accessoryValue.lifeTimeData.energy / 1000) + Number.EPSILON) *10) /10)
that.lifeTime
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
}
}
if(that.battery){
const batteryValues = await getBatteryValues(that);
let chargeLevel = 0;
let chargingState = 2;
let lowBattery = false;
if (!isEmptyObject(batteryValues)) {
chargeLevel = batteryValues.STORAGE.chargeLevel;
lowBattery = batteryValues.STORAGE.critical;
if (batteryValues.STORAGE.status == "Idle") {
chargingState = 0;
}
else {
batteryValues.connections.forEach(element => {
if (element.to == "STORAGE"){
// inProgress
chargingState = 1;
}
else {
chargingState = 0;
}
})
}
}
that.battery
.getCharacteristic(Characteristic.BatteryLevel)
.updateValue(chargeLevel)
.getCharacteristic(Characteristic.ChargingState)
.updateValue(chargingState)
.getCharacteristic(Characteristic.StatusLowBattery)
.updateValue(lowBattery)
}
}
class SolarEdgeInverter {
constructor(log, config) {
this.log = log
this.config = config
this.current = this.config.current;
if(this.current) {
this.currentPower = new Service.LightSensor("Current Power","Current Power");
this.currentWatts = this.config.currentWatts;
}
if(this.config.last_day) {
this.lastDayPower = new Service.LightSensor("Current Day", "Current Day")
}
if(this.config.last_month) {
this.lastMonth = new Service.LightSensor("Current Month", "Current Month")
}
if(this.config.last_year) {
this.lastYear = new Service.LightSensor("Current Year", "Current Year")
}
if(this.config.life_time) {
this.lifeTime = new Service.LightSensor("Life Time", "Life Time")
}
if(this.config.battery) {
this.battery = new Service.BatteryService("Battery Level", "Battery Level")
}
this.name = this.config.name;
this.manufacturer = this.config.manufacturer || "SolarEdge";
this.model = this.config.model || "Inverter";
this.serial = this.config.serial || "solaredge-inverter-1";
this.siteID = this.config.site_id;
this.apiKey = this.config.api_key;
this.update_interval = (this.config.update_interval || 15) * 60 * 1000;
this.debug = this.config.debug || false;
this.minLux = this.config.min_lux || DEF_MIN_LUX;
this.maxLux = this.config.max_lux || DEF_MAX_LUX;
if(this.currentPower || this.lastDayPower || this.lastMonth || this.lastYear || this.lifeTime || this.battery) {
update(this);
setInterval ( async() => {
if(this.currentPower || this.lastDayPower || this.lastMonth || this.lastYear || this.lifeTime) {
const accessoryValue = await getAccessoryValue(this);
let power = 0;
if (accessoryValue) {
if(this.currentPower) {
if(parseFloat(accessoryValue.currentPower.power) > 0) {
if(this.currentWatts) {
power = Math.abs(Math.round((accessoryValue.currentPower.power + Number.EPSILON) *10) /10)
} else {
power = Math.abs(Math.round(((accessoryValue.currentPower.power / 1000) + Number.EPSILON) *10) /10)
}
}
this.currentPower
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(this.lastDayPower) {
power = Math.abs(Math.round(((accessoryValue.lastDayData.energy / 1000) + Number.EPSILON) *10) /10)
this.lastDayPower
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(this.lastMonth) {
power = Math.abs(Math.round(((accessoryValue.lastMonthData.energy / 1000) + Number.EPSILON) *10) /10)
this.lastMonth
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(this.lastYear) {
power = Math.abs(Math.round(((accessoryValue.lastYearData.energy / 1000) + Number.EPSILON) *10) /10)
this.lastYear
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
if(this.lifeTime) {
power = Math.abs(Math.round(((accessoryValue.lifeTimeData.energy / 1000) + Number.EPSILON) *10) /10)
this.lifeTime
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.updateValue(power ? power : 0.0001)
}
}
}
if(this.battery){
const batteryValues = await getBatteryValues(this);
let chargeLevel = 0;
let chargingState = 2;
let lowBattery = false;
if (!isEmptyObject(batteryValues)) {
chargeLevel = batteryValues.STORAGE.chargeLevel;
lowBattery = batteryValues.STORAGE.critical;
if (batteryValues.STORAGE.status == "Idle") {
chargingState = 0;
}
else {
batteryValues.connections.forEach(element => {
if (element.to == "STORAGE"){
// inProgress
chargingState = 1;
}
else {
chargingState = 0;
}
})
}
}
this.battery
.getCharacteristic(Characteristic.BatteryLevel)
.updateValue(chargeLevel)
.getCharacteristic(Characteristic.ChargingState)
.updateValue(chargingState)
.getCharacteristic(Characteristic.StatusLowBattery)
.updateValue(lowBattery)
}
}, this.update_interval)
}
}
getServices () {
const informationService = new Service.AccessoryInformation()
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.serial)
const services = [informationService]
if(this.current) {
this.currentPower.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
services.push(this.currentPower);
}
if(this.lastDayPower) {
this.lastDayPower.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
services.push(this.lastDayPower);
}
if(this.lastMonth) {
this.lastMonth.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
services.push(this.lastMonth);
}
if(this.lastYear) {
this.lastYear.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
services.push(this.lastYear);
}
if(this.lifeTime) {
this.lifeTime.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
services.push(this.lifeTime);
}
if(this.battery) {
this.battery.getCharacteristic(Characteristic.BatteryLevel)
this.battery.getCharacteristic(Characteristic.ChargingState)
this.battery.getCharacteristic(Characteristic.StatusLowBattery)
services.push(this.battery);
}
return services
}
}