@cryptotaxi247 / netdata-1 / commits / 66d8f45e3

First implementation

BrainDoctor committed Jul 4, 2017 at 21:17 UTC 66d8f45e39a9d884ca900dee57d7044989fc52a9
1 file changed +302
node.d/fronius.node.js new
+302
@@ -0,0 +1,302 @@
1 +'use strict';
2 +
3 +// This program will connect to one or more Fronius Symo Inverters.
4 +// to get the Solar Power Generated (current, today).
5 +
6 +// example configuration in /etc/netdata/node.d/fronius.conf
7 +/*
8 + {
9 + "enable_autodetect": false,
10 + "update_every": 5,
11 + "servers": [
12 + {
13 + "name": "plant1",
14 + "hostname": "10.0.1.1",
15 + "update_every": 10,
16 + "api_path": "/solar_api/v1/GetPowerFlowRealtimeData.fcgi"
17 + },
18 + {
19 + "name": "plant2",
20 + "hostname": "10.0.2.1",
21 + "update_every": 15,
22 + "api_path": "/solar_api/v1/GetPowerFlowRealtimeData.fcgi"
23 + }
24 + ]
25 + }
26 + */
27 +
28 +
29 +var url = require('url');
30 +var http = require('http');
31 +var netdata = require('netdata');
32 +
33 +netdata.debug('loaded ' + __filename + ' plugin');
34 +
35 +const power_grid_id = 'p_grid';
36 +const power_pv_id = 'p_pv';
37 +const power_accu_id = 'p_akku'; // not my typo! Using the ID from the API
38 +const consumption_load_id = 'p_load';
39 +
40 +var fronius = {
41 + name: __filename,
42 + enable_autodetect: false,
43 + update_every: 3,
44 + base_priority: 60000,
45 + charts: {},
46 +
47 + createBasicDimension: function (id, name) {
48 + return {
49 + id: id, // the unique id of the dimension
50 + name: name, // the name of the dimension
51 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
52 + multiplier: 1, // the multiplier
53 + divisor: 1, // the divisor
54 + hidden: false // is hidden (boolean)
55 + }
56 + },
57 +
58 + // Gets the site power chart. Will be created if not existing.
59 + getSitePowerChart: function (service, id) {
60 +
61 + var chart = fronius.charts[id];
62 + if (fronius.isDefined(chart)) return chart;
63 +
64 + var dim = {};
65 + dim[power_grid_id] = this.createBasicDimension(power_grid_id, "Grid");
66 + dim[power_pv_id] = this.createBasicDimension(power_pv_id, "Photovoltaics");
67 + dim[power_accu_id] = this.createBasicDimension(power_accu_id, "Accumulator");
68 +
69 + chart = {
70 + id: id, // the unique id of the chart
71 + name: '', // the unique name of the chart
72 + title: service.name + ' Current Site Power', // the title of the chart
73 + units: 'W', // the units of the chart dimensions
74 + family: 'Power', // the family of the chart
75 + context: 'fronius.power', // the context of the chart
76 + type: netdata.chartTypes.line, // the type of the chart
77 + priority: fronius.base_priority + 1, // the priority relative to others in the same family
78 + update_every: service.update_every, // the expected update frequency of the chart
79 + dimensions: dim
80 + };
81 + chart = service.chart(id, chart);
82 + fronius.charts[id] = chart;
83 +
84 + return chart;
85 + },
86 +
87 + // Gets the site consumption chart. Will be created if not existing.
88 + getSiteConsumptionChart: function (service, id) {
89 +
90 + var chart = fronius.charts[id];
91 + if (fronius.isDefined(chart)) return chart;
92 + var dim = {};
93 + dim[consumption_load_id] = this.createBasicDimension(consumption_load_id, "Load");
94 +
95 + chart = {
96 + id: id, // the unique id of the chart
97 + name: '', // the unique name of the chart
98 + title: service.name + ' Current Load', // the title of the chart
99 + units: 'W', // the units of the chart dimensions
100 + family: 'Consumption', // the family of the chart
101 + context: 'fronius.consumption', // the context of the chart
102 + type: netdata.chartTypes.area, // the type of the chart
103 + priority: fronius.base_priority + 2, // the priority relative to others in the same family
104 + update_every: service.update_every, // the expected update frequency of the chart
105 + dimensions: dim
106 + };
107 + chart = service.chart(id, chart);
108 + fronius.charts[id] = chart;
109 +
110 + return chart;
111 + },
112 +
113 + // Gets the inverter power chart. Will be created if not existing.
114 + // Needs the array of inverters in order to create a chart with all inverters as dimensions
115 + getInverterPowerChart: function (service, chartId, inverters) {
116 +
117 + var chart = fronius.charts[chartId];
118 + if (fronius.isDefined(chart)) return chart;
119 +
120 + var dim = {};
121 +
122 + var inverter_count = Object.keys(inverters).length;
123 + var inverter = inverters[inverter_count.toString()];
124 + var i = 1;
125 + for (i; i <= inverter_count; i++) {
126 + if (fronius.isUndefined(inverter)) {
127 + netdata.error("Expected an Inverter with a numerical name! " +
128 + "Have a look at your JSON output to verify.");
129 + continue;
130 + }
131 + dim[i.toString()] = this.createBasicDimension("inverter_" + i, "Inverter " + i);
132 + }
133 +
134 + chart = {
135 + id: chartId, // the unique id of the chart
136 + name: '', // the unique name of the chart
137 + title: service.name + ' Current Inverter Output', // the title of the chart
138 + units: 'W', // the units of the chart dimensions
139 + family: 'Inverters', // the family of the chart
140 + context: 'fronius.inverter', // the context of the chart
141 + type: netdata.chartTypes.stacked, // the type of the chart
142 + priority: fronius.base_priority + 3, // the priority relative to others in the same family
143 + update_every: service.update_every, // the expected update frequency of the chart
144 + dimensions: dim
145 + };
146 + chart = service.chart(chartId, chart);
147 + fronius.charts[chartId] = chart;
148 +
149 + return chart;
150 + },
151 +
152 + // Gets the inverter energy production chart for today. Will be created if not existing.
153 + // Needs the array of inverters in order to create a chart with all inverters as dimensions
154 + getInverterEnergyTodayChart: function (service, chartId, inverters) {
155 +
156 + var chart = fronius.charts[chartId];
157 + if (fronius.isDefined(chart)) return chart;
158 +
159 + var dim = {};
160 +
161 + var inverter_count = Object.keys(inverters).length;
162 + var inverter = inverters[inverter_count.toString()];
163 + var i = 1;
164 + for (i; i <= inverter_count; i++) {
165 + if (fronius.isUndefined(inverter)) {
166 + netdata.error("Expected an Inverter with a numerical name! " +
167 + "Have a look at your JSON output to verify.");
168 + continue;
169 + }
170 + dim[i.toString()] = {
171 + id: 'inverter_' + i, // the unique id of the dimension
172 + name: 'Inverter ' + i, // the name of the dimension
173 + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm
174 + multiplier: 1, // the multiplier
175 + divisor: 1000, // the divisor
176 + hidden: false // is hidden (boolean)
177 + };
178 + }
179 +
180 + chart = {
181 + id: chartId, // the unique id of the chart
182 + name: '', // the unique name of the chart
183 + title: service.name + ' Inverter Energy production for today', // the title of the chart
184 + units: 'kWh', // the units of the chart dimensions
185 + family: 'Inverters', // the family of the chart
186 + context: 'fronius.inverter', // the context of the chart
187 + type: netdata.chartTypes.stacked, // the type of the chart
188 + priority: fronius.base_priority + 4, // the priority relative to others in the same family
189 + update_every: service.update_every, // the expected update frequency of the chart
190 + dimensions: dim
191 + };
192 + chart = service.chart(chartId, chart);
193 + fronius.charts[chartId] = chart;
194 +
195 + return chart;
196 + },
197 +
198 +
199 + processResponse: function (service, content) {
200 + if (content === null) return;
201 + var json = JSON.parse(content);
202 + // validating response
203 + if (fronius.isUndefined(json.Body)) return;
204 + if (fronius.isUndefined(json.Body.Data)) return;
205 + if (fronius.isUndefined(json.Body.Data.Site)) return;
206 + if (fronius.isUndefined(json.Body.Data.Inverters)) return;
207 +
208 + // add the service
209 + if (service.added !== true)
210 + service.commit();
211 +
212 + var site = json.Body.Data.Site;
213 +
214 + // Site Current Power Chart
215 + service.begin(fronius.getSitePowerChart(service, 'fronius_' + service.name + '.power'));
216 + service.set(power_grid_id, Math.round(site.P_Grid));
217 + service.set(power_pv_id, Math.round(site.P_PV));
218 + service.set(power_accu_id, Math.round(site.P_Akku));
219 + service.end();
220 +
221 + // Site Consumption Chart
222 + var consumption = site.P_Load;
223 + if (consumption === null) consumption = 0;
224 + consumption *= -1;
225 +
226 + service.begin(fronius.getSiteConsumptionChart(service, 'fronius_' + service.name + '.consumption'));
227 + service.set(consumption_load_id, Math.round(consumption));
228 + service.end();
229 +
230 + // Inverters
231 + var inverters = json.Body.Data.Inverters;
232 + var inverter_count = Object.keys(inverters).length;
233 + if (inverter_count <= 0) return;
234 + var i = 1;
235 + for (i; i <= inverter_count; i++) {
236 + var inverter = inverters[i];
237 + if (fronius.isUndefined(inverter)) continue;
238 + netdata.debug("Setting values");
239 + service.begin(fronius.getInverterPowerChart(service, 'fronius_' + service.name + '.inverters.output', inverters));
240 + service.set(i.toString(), Math.round(inverter.P));
241 + service.end();
242 + service.begin(fronius.getInverterEnergyTodayChart(service, 'fronius_' + service.name + '.inverters.today', inverters));
243 + service.set(i.toString(), Math.round(inverter.E_Day));
244 + service.end();
245 + }
246 + },
247 +
248 + // module.serviceExecute()
249 + // this function is called only from this module
250 + // its purpose is to prepare the request and call
251 + // netdata.serviceExecute()
252 + serviceExecute: function (name, uri, update_every) {
253 + netdata.debug(this.name + ': ' + name + ': url: ' + uri + ', update_every: ' + update_every);
254 +
255 + var service = netdata.service({
256 + name: name,
257 + request: netdata.requestFromURL('http://' + uri),
258 + update_every: update_every,
259 + module: this
260 + });
261 + service.request.method = 'GET';
262 + service.execute(this.processResponse);
263 + },
264 +
265 +
266 + configure: function (config) {
267 + if (fronius.isUndefined(config.servers)) return 0;
268 + var added = 0;
269 + var len = config.servers.length;
270 + while (len--) {
271 + var server = config.servers[len];
272 + if (fronius.isUndefined(server.update_every)) server.update_every = this.update_every;
273 +
274 + var url = server.hostname + server.api_path;
275 + this.serviceExecute(server.name, url, server.update_every);
276 + added++;
277 + }
278 + return added;
279 + },
280 +
281 + // module.update()
282 + // this is called repeatedly to collect data, by calling
283 + // netdata.serviceExecute()
284 + update: function (service, callback) {
285 + service.execute(function (serv, data) {
286 + service.module.processResponse(serv, data);
287 + callback();
288 + });
289 + },
290 +
291 + isUndefined: function (value) {
292 + return typeof value === 'undefined';
293 + },
294 +
295 + isDefined: function (value) {
296 + return typeof value !== 'undefined';
297 + }
298 +
299 +
300 +};
301 +
302 +module.exports = fronius;