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;