summaryrefslogtreecommitdiffstats
path: root/node.d/fronius.node.js
diff options
context:
space:
mode:
Diffstat (limited to 'node.d/fronius.node.js')
-rw-r--r--node.d/fronius.node.js317
1 files changed, 317 insertions, 0 deletions
diff --git a/node.d/fronius.node.js b/node.d/fronius.node.js
new file mode 100644
index 00000000..f771f6c3
--- /dev/null
+++ b/node.d/fronius.node.js
@@ -0,0 +1,317 @@
+'use strict';
+
+// This program will connect to one or more Fronius Symo Inverters.
+// to get the Solar Power Generated (current, today).
+
+// example configuration in netdata/conf.d/node.d/fronius.conf.md
+
+var url = require('url');
+var http = require('http');
+var netdata = require('netdata');
+
+netdata.debug('loaded ' + __filename + ' plugin');
+
+var fronius = {
+ name: "Fronius",
+ enable_autodetect: false,
+ update_every: 5,
+ base_priority: 60000,
+ charts: {},
+
+ powerGridId: "p_grid",
+ powerPvId: "p_pv",
+ powerAccuId: "p_akku", // not my typo! Using the ID from the AP
+ consumptionLoadId: "p_load",
+ autonomyId: "rel_autonomy",
+ consumptionSelfId: "rel_selfconsumption",
+ energyTodayId: "e_day",
+ energyYearId: "e_year",
+
+ createBasicDimension: function (id, name, divisor) {
+ return {
+ id: id, // the unique id of the dimension
+ name: name, // the name of the dimension
+ algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
+ multiplier: 1, // the multiplier
+ divisor: divisor, // the divisor
+ hidden: false // is hidden (boolean)
+ };
+ },
+
+ // Gets the site power chart. Will be created if not existing.
+ getSitePowerChart: function (service, id) {
+ var chart = fronius.charts[id];
+ if (fronius.isDefined(chart)) return chart;
+
+ var dim = {};
+ dim[fronius.powerGridId] = this.createBasicDimension(fronius.powerGridId, "Grid", 1);
+ dim[fronius.powerPvId] = this.createBasicDimension(fronius.powerPvId, "Photovoltaics", 1);
+ dim[fronius.powerAccuId] = this.createBasicDimension(fronius.powerAccuId, "Accumulator", 1);
+
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Current Site Power', // the title of the chart
+ units: 'W', // the units of the chart dimensions
+ family: 'power', // the family of the chart
+ context: 'fronius.power', // the context of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: fronius.base_priority + 1, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(id, chart);
+ fronius.charts[id] = chart;
+
+ return chart;
+ },
+
+ // Gets the site consumption chart. Will be created if not existing.
+ getSiteConsumptionChart: function (service, id) {
+ var chart = fronius.charts[id];
+ if (fronius.isDefined(chart)) return chart;
+ var dim = {};
+ dim[fronius.consumptionLoadId] = this.createBasicDimension(fronius.consumptionLoadId, "Load", 1);
+
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Current Load', // the title of the chart
+ units: 'W', // the units of the chart dimensions
+ family: 'consumption', // the family of the chart
+ context: 'fronius.consumption', // the context of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: fronius.base_priority + 2, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(id, chart);
+ fronius.charts[id] = chart;
+
+ return chart;
+ },
+
+ // Gets the site consumption chart. Will be created if not existing.
+ getSiteAutonomyChart: function (service, id) {
+ var chart = fronius.charts[id];
+ if (fronius.isDefined(chart)) return chart;
+ var dim = {};
+ dim[fronius.autonomyId] = this.createBasicDimension(fronius.autonomyId, "Autonomy", 1);
+ dim[fronius.consumptionSelfId] = this.createBasicDimension(fronius.consumptionSelfId, "Self Consumption", 1);
+
+ chart = {
+ id: id, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Current Autonomy', // the title of the chart
+ units: '%', // the units of the chart dimensions
+ family: 'autonomy', // the family of the chart
+ context: 'fronius.autonomy', // the context of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: fronius.base_priority + 3, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(id, chart);
+ fronius.charts[id] = chart;
+
+ return chart;
+ },
+
+ // Gets the site energy chart for today. Will be created if not existing.
+ getSiteEnergyTodayChart: function (service, chartId) {
+ var chart = fronius.charts[chartId];
+ if (fronius.isDefined(chart)) return chart;
+ var dim = {};
+ dim[fronius.energyTodayId] = this.createBasicDimension(fronius.energyTodayId, "Today", 1000);
+ chart = {
+ id: chartId, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Energy production for today', // the title of the chart
+ units: 'kWh', // the units of the chart dimensions
+ family: 'energy', // the family of the chart
+ context: 'fronius.energy.today', // the context of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: fronius.base_priority + 4, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(chartId, chart);
+ fronius.charts[chartId] = chart;
+
+ return chart;
+ },
+
+ // Gets the site energy chart for today. Will be created if not existing.
+ getSiteEnergyYearChart: function (service, chartId) {
+ var chart = fronius.charts[chartId];
+ if (fronius.isDefined(chart)) return chart;
+ var dim = {};
+ dim[fronius.energyYearId] = this.createBasicDimension(fronius.energyYearId, "Year", 1000);
+ chart = {
+ id: chartId, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Energy production for this year', // the title of the chart
+ units: 'kWh', // the units of the chart dimensions
+ family: 'energy', // the family of the chart
+ context: 'fronius.energy.year', // the context of the chart
+ type: netdata.chartTypes.area, // the type of the chart
+ priority: fronius.base_priority + 5, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(chartId, chart);
+ fronius.charts[chartId] = chart;
+
+ return chart;
+ },
+
+ // Gets the inverter power chart. Will be created if not existing.
+ // Needs the array of inverters in order to create a chart with all inverters as dimensions
+ getInverterPowerChart: function (service, chartId, inverters) {
+
+ var chart = fronius.charts[chartId];
+ if (fronius.isDefined(chart)) return chart;
+
+ var dim = {};
+
+ var inverterCount = Object.keys(inverters).length;
+ var inverter = inverters[inverterCount.toString()];
+ var i = 1;
+ for (i; i <= inverterCount; i++) {
+ if (fronius.isUndefined(inverter)) {
+ netdata.error("Expected an Inverter with a numerical name! " +
+ "Have a look at your JSON output to verify.");
+ continue;
+ }
+ dim[i.toString()] = this.createBasicDimension("inverter_" + i, "Inverter " + i, 1);
+ }
+
+ chart = {
+ id: chartId, // the unique id of the chart
+ name: '', // the unique name of the chart
+ title: service.name + ' Current Inverter Output', // the title of the chart
+ units: 'W', // the units of the chart dimensions
+ family: 'inverters', // the family of the chart
+ context: 'fronius.inverter.output', // the context of the chart
+ type: netdata.chartTypes.stacked, // the type of the chart
+ priority: fronius.base_priority + 6, // the priority relative to others in the same family
+ update_every: service.update_every, // the expected update frequency of the chart
+ dimensions: dim
+ };
+ chart = service.chart(chartId, chart);
+ fronius.charts[chartId] = chart;
+
+ return chart;
+ },
+
+ processResponse: function (service, content) {
+ if (content === null) return;
+ var json = JSON.parse(content);
+ if (!fronius.isResponseValid(json)) return;
+
+ // add the service
+ service.commit();
+
+ var site = json.Body.Data.Site;
+
+ // Site Current Power Chart
+ service.begin(fronius.getSitePowerChart(service, 'fronius_' + service.name + '.power'));
+ service.set(fronius.powerGridId, Math.round(site.P_Grid));
+ service.set(fronius.powerPvId, Math.round(site.P_PV));
+ service.set(fronius.powerAccuId, Math.round(site.P_Akku));
+ service.end();
+
+ // Site Consumption Chart
+ service.begin(fronius.getSiteConsumptionChart(service, 'fronius_' + service.name + '.consumption'));
+ service.set(fronius.consumptionLoadId, Math.round(Math.abs(site.P_Load)));
+ service.end();
+
+ // Site Autonomy Chart
+ service.begin(fronius.getSiteAutonomyChart(service, 'fronius_' + service.name + '.autonomy'));
+ service.set(fronius.autonomyId, Math.round(site.rel_Autonomy));
+ var selfConsumption = site.rel_SelfConsumption;
+ service.set(fronius.consumptionSelfId, Math.round(selfConsumption === null ? 100 : selfConsumption));
+ service.end();
+
+ // Site Energy Today Chart
+ service.begin(fronius.getSiteEnergyTodayChart(service, 'fronius_' + service.name + '.energy.today'));
+ service.set(fronius.energyTodayId, Math.round(site.E_Day));
+ service.end();
+
+ // Site Energy Year Chart
+ service.begin(fronius.getSiteEnergyYearChart(service, 'fronius_' + service.name + '.energy.year'));
+ service.set(fronius.energyYearId, Math.round(site.E_Year));
+ service.end();
+
+ // Inverters
+ var inverters = json.Body.Data.Inverters;
+ var inverterCount = Object.keys(inverters).length + 1;
+ while (inverterCount--) {
+ var inverter = inverters[inverterCount];
+ if (fronius.isUndefined(inverter)) continue;
+ service.begin(fronius.getInverterPowerChart(service, 'fronius_' + service.name + '.inverters.output', inverters));
+ service.set(inverterCount.toString(), Math.round(inverter.P));
+ service.end();
+ }
+ },
+
+ // some basic validation
+ isResponseValid: function (json) {
+ if (fronius.isUndefined(json.Body)) return false;
+ if (fronius.isUndefined(json.Body.Data)) return false;
+ if (fronius.isUndefined(json.Body.Data.Site)) return false;
+ return fronius.isDefined(json.Body.Data.Inverters);
+ },
+
+ // module.serviceExecute()
+ // this function is called only from this module
+ // its purpose is to prepare the request and call
+ // netdata.serviceExecute()
+ serviceExecute: function (name, uri, update_every) {
+ netdata.debug(this.name + ': ' + name + ': url: ' + uri + ', update_every: ' + update_every);
+
+ var service = netdata.service({
+ name: name,
+ request: netdata.requestFromURL('http://' + uri),
+ update_every: update_every,
+ module: this
+ });
+ service.execute(this.processResponse);
+ },
+
+
+ configure: function (config) {
+ if (fronius.isUndefined(config.servers)) return 0;
+ var added = 0;
+ var len = config.servers.length;
+ while (len--) {
+ var server = config.servers[len];
+ if (fronius.isUndefined(server.update_every)) server.update_every = this.update_every;
+
+ var url = server.hostname + server.api_path;
+ this.serviceExecute(server.name, url, server.update_every);
+ added++;
+ }
+ return added;
+ },
+
+ // module.update()
+ // this is called repeatedly to collect data, by calling
+ // netdata.serviceExecute()
+ update: function (service, callback) {
+ service.execute(function (serv, data) {
+ service.module.processResponse(serv, data);
+ callback();
+ });
+ },
+
+ isUndefined: function (value) {
+ return typeof value === 'undefined';
+ },
+
+ isDefined: function (value) {
+ return typeof value !== 'undefined';
+ }
+};
+
+module.exports = fronius;