Even more tests. Also improved code quality with a refactoring for better testing.
BrainDoctor committed
Jul 20, 2017 at 14:58 UTC
3a20750bcad67672801bddffc4614fac59ba3ed0
5 files changed
+611
-190
node.d/fronius.node.js
+162
-108
@@ -1,15 +1,15 @@
1
-'use strict';
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 netdata/conf.d/node.d/fronius.conf.md
7
8
-var url = require('url');
9
-var http = require('http');
10
-var netdata = require('netdata');
8
+var url = require("url");
9
+var http = require("http");
10
+var netdata = require("netdata");
11
12
-netdata.debug('loaded ' + __filename + ' plugin');
12
+netdata.debug("loaded " + __filename + " plugin");
13
14
var fronius = {
15
name: "Fronius",
@@ -39,7 +39,8 @@ var fronius = {
39
},
40
41
// Gets the site power chart. Will be created if not existing.
42
- getSitePowerChart: function (service, id) {
42
+ getSitePowerChart: function (service, suffix) {
43
+ var id = this.getChartId(service, suffix);
44
var chart = fronius.charts[id];
45
if (fronius.isDefined(chart)) return chart;
46
@@ -50,13 +51,13 @@ var fronius = {
51
52
chart = {
53
id: id, // the unique id of the chart
53
- name: '', // the unique name of the chart
54
- title: service.name + ' Current Site Power', // the title of the chart
55
- units: 'W', // the units of the chart dimensions
56
- family: 'power', // the family of the chart
57
- context: 'fronius.power', // the context of the chart
54
+ name: "", // the unique name of the chart
55
+ title: service.name + " Current Site Power", // the title of the chart
56
+ units: "W", // the units of the chart dimensions
57
+ family: "power", // the family of the chart
58
+ context: "fronius.power", // the context of the chart
59
type: netdata.chartTypes.area, // the type of the chart
59
- priority: fronius.base_priority + 1, // the priority relative to others in the same family
60
+ priority: fronius.base_priority + 1, // the priority relative to others in the same family
61
update_every: service.update_every, // the expected update frequency of the chart
62
dimensions: dim
63
};
@@ -67,7 +68,8 @@ var fronius = {
68
},
69
70
// Gets the site consumption chart. Will be created if not existing.
70
- getSiteConsumptionChart: function (service, id) {
71
+ getSiteConsumptionChart: function (service, suffix) {
72
+ var id = this.getChartId(service, suffix);
73
var chart = fronius.charts[id];
74
if (fronius.isDefined(chart)) return chart;
75
var dim = {};
@@ -75,11 +77,11 @@ var fronius = {
77
78
chart = {
79
id: id, // the unique id of the chart
78
- name: '', // the unique name of the chart
79
- title: service.name + ' Current Load', // the title of the chart
80
- units: 'W', // the units of the chart dimensions
81
- family: 'consumption', // the family of the chart
82
- context: 'fronius.consumption', // the context of the chart
80
+ name: "", // the unique name of the chart
81
+ title: service.name + " Current Load", // the title of the chart
82
+ units: "W", // the units of the chart dimensions
83
+ family: "consumption", // the family of the chart
84
+ context: "fronius.consumption", // the context of the chart
85
type: netdata.chartTypes.area, // the type of the chart
86
priority: fronius.base_priority + 2, // the priority relative to others in the same family
87
update_every: service.update_every, // the expected update frequency of the chart
@@ -92,7 +94,8 @@ var fronius = {
94
},
95
96
// Gets the site consumption chart. Will be created if not existing.
95
- getSiteAutonomyChart: function (service, id) {
97
+ getSiteAutonomyChart: function (service, suffix) {
98
+ var id = this.getChartId(service, suffix);
99
var chart = fronius.charts[id];
100
if (fronius.isDefined(chart)) return chart;
101
var dim = {};
@@ -101,11 +104,11 @@ var fronius = {
104
105
chart = {
106
id: id, // the unique id of the chart
104
- name: '', // the unique name of the chart
105
- title: service.name + ' Current Autonomy', // the title of the chart
106
- units: '%', // the units of the chart dimensions
107
- family: 'autonomy', // the family of the chart
108
- context: 'fronius.autonomy', // the context of the chart
107
+ name: "", // the unique name of the chart
108
+ title: service.name + " Current Autonomy", // the title of the chart
109
+ units: "%", // the units of the chart dimensions
110
+ family: "autonomy", // the family of the chart
111
+ context: "fronius.autonomy", // the context of the chart
112
type: netdata.chartTypes.area, // the type of the chart
113
priority: fronius.base_priority + 3, // the priority relative to others in the same family
114
update_every: service.update_every, // the expected update frequency of the chart
@@ -118,21 +121,22 @@ var fronius = {
121
},
122
123
// Gets the site energy chart for today. Will be created if not existing.
121
- getSiteEnergyTodayChart: function (service, chartId) {
124
+ getSiteEnergyTodayChart: function (service, suffix) {
125
+ var chartId = this.getChartId(service, suffix);
126
var chart = fronius.charts[chartId];
127
if (fronius.isDefined(chart)) return chart;
128
var dim = {};
129
dim[fronius.energyTodayId] = this.createBasicDimension(fronius.energyTodayId, "Today", 1000);
130
chart = {
131
id: chartId, // the unique id of the chart
128
- name: '', // the unique name of the chart
129
- title: service.name + ' Energy production for today', // the title of the chart
130
- units: 'kWh', // the units of the chart dimensions
131
- family: 'energy', // the family of the chart
132
- context: 'fronius.energy.today', // the context of the chart
133
- type: netdata.chartTypes.area, // the type of the chart
134
- priority: fronius.base_priority + 4, // the priority relative to others in the same family
135
- update_every: service.update_every, // the expected update frequency of the chart
132
+ name: "", // the unique name of the chart
133
+ title: service.name + " Energy production for today",// the title of the chart
134
+ units: "kWh", // the units of the chart dimensions
135
+ family: "energy", // the family of the chart
136
+ context: "fronius.energy.today", // the context of the chart
137
+ type: netdata.chartTypes.area, // the type of the chart
138
+ priority: fronius.base_priority + 4, // the priority relative to others in the same family
139
+ update_every: service.update_every, // the expected update frequency of the chart
140
dimensions: dim
141
};
142
chart = service.chart(chartId, chart);
@@ -142,21 +146,22 @@ var fronius = {
146
},
147
148
// Gets the site energy chart for today. Will be created if not existing.
145
- getSiteEnergyYearChart: function (service, chartId) {
149
+ getSiteEnergyYearChart: function (service, suffix) {
150
+ var chartId = this.getChartId(service, suffix);
151
var chart = fronius.charts[chartId];
152
if (fronius.isDefined(chart)) return chart;
153
var dim = {};
154
dim[fronius.energyYearId] = this.createBasicDimension(fronius.energyYearId, "Year", 1000);
155
chart = {
151
- id: chartId, // the unique id of the chart
152
- name: '', // the unique name of the chart
153
- title: service.name + ' Energy production for this year', // the title of the chart
154
- units: 'kWh', // the units of the chart dimensions
155
- family: 'energy', // the family of the chart
156
- context: 'fronius.energy.year', // the context of the chart
157
- type: netdata.chartTypes.area, // the type of the chart
158
- priority: fronius.base_priority + 5, // the priority relative to others in the same family
159
- update_every: service.update_every, // the expected update frequency of the chart
156
+ id: chartId, // the unique id of the chart
157
+ name: "", // the unique name of the chart
158
+ title: service.name + " Energy production for this year",// the title of the chart
159
+ units: "kWh", // the units of the chart dimensions
160
+ family: "energy", // the family of the chart
161
+ context: "fronius.energy.year", // the context of the chart
162
+ type: netdata.chartTypes.area, // the type of the chart
163
+ priority: fronius.base_priority + 5, // the priority relative to others in the same family
164
+ update_every: service.update_every, // the expected update frequency of the chart
165
dimensions: dim
166
};
167
chart = service.chart(chartId, chart);
@@ -167,35 +172,28 @@ var fronius = {
172
173
// Gets the inverter power chart. Will be created if not existing.
174
// Needs the array of inverters in order to create a chart with all inverters as dimensions
170
- getInverterPowerChart: function (service, chartId, inverters) {
171
-
175
+ getInverterPowerChart: function (service, suffix, inverters) {
176
+ var chartId = this.getChartId(service, suffix);
177
var chart = fronius.charts[chartId];
178
if (fronius.isDefined(chart)) return chart;
179
180
var dim = {};
176
-
177
- var inverterCount = Object.keys(inverters).length;
178
- var inverter = inverters[inverterCount.toString()];
179
- var i = 1;
180
- for (i; i <= inverterCount; i++) {
181
- if (fronius.isUndefined(inverter)) {
182
- netdata.error("Expected an Inverter with a numerical name! " +
183
- "Have a look at your JSON output to verify.");
184
- continue;
185
- }
186
- dim[i.toString()] = this.createBasicDimension("inverter_" + i, "Inverter " + i, 1);
181
+ for (var key in inverters) {
182
+ var name = key;
183
+ if (!isNaN(key)) name = "Inverter " + key;
184
+ dim[key] = this.createBasicDimension("inverter_" + key, name, 1);
185
}
186
187
chart = {
190
- id: chartId, // the unique id of the chart
191
- name: '', // the unique name of the chart
192
- title: service.name + ' Current Inverter Output', // the title of the chart
193
- units: 'W', // the units of the chart dimensions
194
- family: 'inverters', // the family of the chart
195
- context: 'fronius.inverter.output', // the context of the chart
196
- type: netdata.chartTypes.stacked, // the type of the chart
188
+ id: chartId, // the unique id of the chart
189
+ name: "", // the unique name of the chart
190
+ title: service.name + " Current Inverter Output",// the title of the chart
191
+ units: "W", // the units of the chart dimensions
192
+ family: "inverters", // the family of the chart
193
+ context: "fronius.inverter.output", // the context of the chart
194
+ type: netdata.chartTypes.stacked, // the type of the chart
195
priority: fronius.base_priority + 6, // the priority relative to others in the same family
198
- update_every: service.update_every, // the expected update frequency of the chart
196
+ update_every: service.update_every, // the expected update frequency of the chart
197
dimensions: dim
198
};
199
chart = service.chart(chartId, chart);
@@ -205,59 +203,115 @@ var fronius = {
203
},
204
205
processResponse: function (service, content) {
208
- var json = fronius.parseResponse(content);
206
+ var json = fronius.convertToJson(content);
207
if (json === null) return;
208
209
// add the service
210
service.commit();
211
212
+ var chartDefinitions = fronius.parseCharts(service, json);
213
+ var chartCount = chartDefinitions.length;
214
+ while (chartCount--) {
215
+ var chartObj = chartDefinitions[chartCount];
216
+ service.begin(chartObj.chart);
217
+ var dimCount = chartObj.dimensions.length;
218
+ while (dimCount--) {
219
+ var dim = chartObj.dimensions[dimCount];
220
+ service.set(dim.name, dim.value);
221
+ }
222
+ service.end();
223
+ }
224
+ },
225
+
226
+ parseCharts: function (service, json) {
227
var site = json.Body.Data.Site;
228
+ return [
229
+ this.parsePowerChart(service, site),
230
+ this.parseConsumptionChart(service, site),
231
+ this.parseAutonomyChart(service, site),
232
+ this.parseEnergyTodayChart(service, site),
233
+ this.parseEnergyYearChart(service, site),
234
+ this.parseInverterChart(service, json.Body.Data.Inverters)
235
+ ];
236
+ },
237
+
238
+ parsePowerChart: function (service, site) {
239
+ return this.getChart(this.getSitePowerChart(service, "power"),
240
+ [
241
+ this.getDimension(this.powerGridId, Math.round(site.P_Grid)),
242
+ this.getDimension(this.powerPvId, Math.round(Math.max(site.P_PV, 0))),
243
+ this.getDimension(this.powerAccuId, Math.round(site.P_Akku))
244
+ ]
245
+ );
246
+ },
247
+
248
+ parseConsumptionChart: function (service, site) {
249
+ return this.getChart(this.getSiteConsumptionChart(service, "consumption"),
250
+ [this.getDimension(this.consumptionLoadId, Math.round(Math.abs(site.P_Load)))]
251
+ );
252
+ },
253
216
- // Site Current Power Chart
217
- service.begin(fronius.getSitePowerChart(service, 'fronius_' + service.name + '.power'));
218
- service.set(fronius.powerGridId, Math.round(site.P_Grid));
219
- service.set(fronius.powerPvId, Math.round(site.P_PV));
220
- service.set(fronius.powerAccuId, Math.round(site.P_Akku));
221
- service.end();
222
-
223
- // Site Consumption Chart
224
- service.begin(fronius.getSiteConsumptionChart(service, 'fronius_' + service.name + '.consumption'));
225
- service.set(fronius.consumptionLoadId, Math.round(Math.abs(site.P_Load)));
226
- service.end();
227
-
228
- // Site Autonomy Chart
229
- service.begin(fronius.getSiteAutonomyChart(service, 'fronius_' + service.name + '.autonomy'));
230
- service.set(fronius.autonomyId, Math.round(site.rel_Autonomy));
254
+ parseAutonomyChart: function (service, site) {
255
var selfConsumption = site.rel_SelfConsumption;
232
- service.set(fronius.consumptionSelfId, Math.round(selfConsumption === null ? 100 : selfConsumption));
233
- service.end();
234
-
235
- // Site Energy Today Chart
236
- service.begin(fronius.getSiteEnergyTodayChart(service, 'fronius_' + service.name + '.energy.today'));
237
- service.set(fronius.energyTodayId, Math.round(site.E_Day));
238
- service.end();
239
-
240
- // Site Energy Year Chart
241
- service.begin(fronius.getSiteEnergyYearChart(service, 'fronius_' + service.name + '.energy.year'));
242
- service.set(fronius.energyYearId, Math.round(site.E_Year));
243
- service.end();
244
-
245
- // Inverters
246
- var inverters = json.Body.Data.Inverters;
247
- var inverterCount = Object.keys(inverters).length + 1;
248
- while (inverterCount--) {
249
- var inverter = inverters[inverterCount];
250
- if (fronius.isUndefined(inverter)) continue;
251
- service.begin(fronius.getInverterPowerChart(service, 'fronius_' + service.name + '.inverters.output', inverters));
252
- service.set(inverterCount.toString(), Math.round(inverter.P));
253
- service.end();
256
+ return this.getChart(this.getSiteAutonomyChart(service, "autonomy"),
257
+ [
258
+ this.getDimension(this.autonomyId, Math.round(site.rel_Autonomy)),
259
+ this.getDimension(this.consumptionSelfId, Math.round(selfConsumption === null ? 100 : selfConsumption))
260
+ ]
261
+ );
262
+ },
263
+
264
+ parseEnergyTodayChart: function (service, site) {
265
+ return this.getChart(this.getSiteEnergyTodayChart(service, "energy.today"),
266
+ [this.getDimension(this.energyTodayId, Math.round(Math.max(site.E_Day, 0)))]
267
+ );
268
+ },
269
+
270
+ parseEnergyYearChart: function (service, site) {
271
+ return this.getChart(this.getSiteEnergyYearChart(service, "energy.year"),
272
+ [this.getDimension(this.energyYearId, Math.round(Math.max(site.E_Year, 0)))]
273
+ );
274
+ },
275
+
276
+ parseInverterChart: function (service, inverters) {
277
+ var dimensions = [];
278
+ for (var key in inverters) {
279
+ dimensions.push(this.getDimension(key, Math.round(inverters[key].P)));
280
}
281
+ return this.getChart(this.getInverterPowerChart(service, "inverters.output", inverters), dimensions);
282
+ },
283
+
284
+ getDimension: function (name, value) {
285
+ return {
286
+ name: name,
287
+ value: value
288
+ };
289
+ },
290
+
291
+ getChart: function (chart, dimensions) {
292
+ return {
293
+ chart: chart,
294
+ dimensions: dimensions
295
+ };
296
+ },
297
+
298
+ getChartId: function (service, suffix) {
299
+ return "fronius_" + service.name + "." + suffix;
300
},
301
257
- parseResponse: function (httpBody) {
302
+ convertToJson: function (httpBody) {
303
if (httpBody === null) return null;
304
var json = httpBody;
260
- if (typeof httpBody !== "object") json = JSON.parse(httpBody);
305
+ // can't parse if it's already a json object,
306
+ // the check enables easier testing if the httpBody is already valid JSON.
307
+ if (typeof httpBody !== "object") {
308
+ try {
309
+ json = JSON.parse(httpBody);
310
+ } catch (error) {
311
+ netdata.error("fronius: Got a response, but it is not valid JSON. Ignoring. Error: " + error.message);
312
+ return null;
313
+ }
314
+ }
315
return this.isResponseValid(json) ? json : null;
316
},
317
@@ -274,11 +328,11 @@ var fronius = {
328
// its purpose is to prepare the request and call
329
// netdata.serviceExecute()
330
serviceExecute: function (name, uri, update_every) {
277
- netdata.debug(this.name + ': ' + name + ': url: ' + uri + ', update_every: ' + update_every);
331
+ netdata.debug(this.name + ": " + name + ": url: " + uri + ", update_every: " + update_every);
332
333
var service = netdata.service({
334
name: name,
281
- request: netdata.requestFromURL('http://' + uri),
335
+ request: netdata.requestFromURL("http://" + uri),
336
update_every: update_every,
337
module: this
338
});
@@ -313,7 +367,7 @@ var fronius = {
367
},
368
369
isUndefined: function (value) {
316
- return typeof value === 'undefined';
370
+ return typeof value === "undefined";
371
},
372
373
areUndefined: function (valueArray) {
@@ -325,7 +379,7 @@ var fronius = {
379
},
380
381
isDefined: function (value) {
328
- return typeof value !== 'undefined';
382
+ return typeof value !== "undefined";
383
}
384
};
385
tests/node.d/fronius.chart.spec.js
+75
-16
@@ -5,17 +5,23 @@ var netdata = require("../../node.d/node_modules/netdata");
5
var subject = require("../../node.d/fronius.node");
6
7
var service = netdata.service({
8
- name: "fronius",
8
+ name: "chart",
9
module: this
10
});
11
12
describe("fronius chart creation", function () {
13
14
+ var chartPrefix = "fronius_chart.";
15
+
16
beforeAll(function () {
17
// change this to enable debug log
18
netdata.options.DEBUG = false;
19
});
20
21
+ afterAll(function () {
22
+ deleteProperties(subject.charts)
23
+ });
24
+
25
it("should return a basic chart dimension", function () {
26
var result = subject.createBasicDimension("id", "name", 2);
27
@@ -26,10 +32,10 @@ describe("fronius chart creation", function () {
32
});
33
34
it("should return the power chart definition", function () {
29
- var id = "power";
30
- var result = subject.getSitePowerChart(service, id);
35
+ var suffix = "power";
36
+ var result = subject.getSitePowerChart(service, suffix);
37
32
- expect(result.id).toBe(id);
38
+ expect(result.id).toBe(chartPrefix + suffix);
39
expect(result.units).toBe("W");
40
expect(result.type).toBe(netdata.chartTypes.area);
41
expect(result.family).toBe("power");
@@ -41,10 +47,10 @@ describe("fronius chart creation", function () {
47
});
48
49
it("should return the consumption chart definition", function () {
44
- var id = "Load";
45
- var result = subject.getSiteConsumptionChart(service, id);
50
+ var suffix = "Load";
51
+ var result = subject.getSiteConsumptionChart(service, suffix);
52
47
- expect(result.id).toBe(id);
53
+ expect(result.id).toBe(chartPrefix + suffix);
54
expect(result.units).toBe("W");
55
expect(result.type).toBe(netdata.chartTypes.area);
56
expect(result.family).toBe("consumption");
@@ -54,10 +60,10 @@ describe("fronius chart creation", function () {
60
});
61
62
it("should return the autonomy chart definition", function () {
57
- var id = "Autonomy";
58
- var result = subject.getSiteAutonomyChart(service, id);
63
+ var suffix = "Autonomy";
64
+ var result = subject.getSiteAutonomyChart(service, suffix);
65
60
- expect(result.id).toBe(id);
66
+ expect(result.id).toBe(chartPrefix + suffix);
67
expect(result.units).toBe("%");
68
expect(result.type).toBe(netdata.chartTypes.area);
69
expect(result.family).toBe("autonomy");
@@ -68,10 +74,10 @@ describe("fronius chart creation", function () {
74
});
75
76
it("should return the energy today chart definition", function () {
71
- var id = "Energy today";
72
- var result = subject.getSiteEnergyTodayChart(service, id);
77
+ var suffix = "Energy today";
78
+ var result = subject.getSiteEnergyTodayChart(service, suffix);
79
74
- expect(result.id).toBe(id);
80
+ expect(result.id).toBe(chartPrefix + suffix);
81
expect(result.units).toBe("kWh");
82
expect(result.type).toBe(netdata.chartTypes.area);
83
expect(result.family).toBe("energy");
@@ -81,10 +87,10 @@ describe("fronius chart creation", function () {
87
});
88
89
it("should return the energy year chart definition", function () {
84
- var id = "Energy year";
85
- var result = subject.getSiteEnergyYearChart(service, id);
90
+ var suffix = "Energy year";
91
+ var result = subject.getSiteEnergyYearChart(service, suffix);
92
87
- expect(result.id).toBe(id);
93
+ expect(result.id).toBe(chartPrefix + suffix);
94
expect(result.units).toBe("kWh");
95
expect(result.type).toBe(netdata.chartTypes.area);
96
expect(result.family).toBe("energy");
@@ -93,6 +99,59 @@ describe("fronius chart creation", function () {
99
expect(result.dimensions[subject.energyYearId].name).toBe("Year");
100
});
101
102
+ it("should return the inverter chart definition with a single numerical inverter", function () {
103
+ var inverters = {
104
+ "1": {}
105
+ };
106
+ var suffix = "numerical";
107
+ var result = subject.getInverterPowerChart(service, suffix, inverters);
108
+
109
+ expect(result.id).toBe(chartPrefix + suffix);
110
+ expect(result.units).toBe("W");
111
+ expect(result.type).toBe(netdata.chartTypes.stacked);
112
+ expect(result.family).toBe("inverters");
113
+ expect(result.context).toBe("fronius.inverter.output");
114
+ expect(Object.keys(result.dimensions).length).toBe(1);
115
+ expect(result.dimensions["1"].name).toBe("Inverter 1");
116
+ });
117
+
118
+ it("should return the inverter chart definition with a single alphabetical inverter", function () {
119
+ var key = "Cellar";
120
+ var inverters = {
121
+ "Cellar": {}
122
+ };
123
+ var suffix = "alphabetical";
124
+ var result = subject.getInverterPowerChart(service, suffix, inverters);
125
+
126
+ expect(result.id).toBe(chartPrefix + suffix);
127
+ expect(result.units).toBe("W");
128
+ expect(result.type).toBe(netdata.chartTypes.stacked);
129
+ expect(result.family).toBe("inverters");
130
+ expect(result.context).toBe("fronius.inverter.output");
131
+ expect(Object.keys(result.dimensions).length).toBe(1);
132
+ expect(result.dimensions[key].name).toBe(key);
133
+ });
134
+
135
+ it("should return the inverter chart definition with multiple alphanumerical inverter", function () {
136
+ var alpha = "Cellar";
137
+ var numerical = 1;
138
+ var inverters = {
139
+ "Cellar": {},
140
+ "1": {}
141
+ };
142
+ var suffix = "alphanumerical";
143
+ var result = subject.getInverterPowerChart(service, suffix, inverters);
144
+
145
+ expect(result.id).toBe(chartPrefix + suffix);
146
+ expect(result.units).toBe("W");
147
+ expect(result.type).toBe(netdata.chartTypes.stacked);
148
+ expect(result.family).toBe("inverters");
149
+ expect(result.context).toBe("fronius.inverter.output");
150
+ expect(Object.keys(result.dimensions).length).toBe(2);
151
+ expect(result.dimensions[alpha].name).toBe(alpha);
152
+ expect(result.dimensions[numerical].name).toBe("Inverter " + numerical);
153
+ });
154
+
155
it("should return the same chart definition on second call for lazy loading", function () {
156
var first = subject.getSitePowerChart(service, "id");
157
var second = subject.getSitePowerChart(service, "id");
tests/node.d/fronius.parse.spec.js
+288
-50
@@ -5,63 +5,301 @@ var netdata = require("../../node.d/node_modules/netdata");
5
var subject = require("../../node.d/fronius.node");
6
7
var service = netdata.service({
8
- name: "fronius",
8
+ name: "parse",
9
module: this
10
});
11
12
-describe("fronius data parsing", function () {
13
-
14
- var fakeResponse = {
15
- "Head" : {
16
- "RequestArguments" : {},
17
- "Status" : {
18
- "Code" : 0,
19
- "Reason" : "",
20
- "UserMessage" : ""
21
- },
22
- "Timestamp" : "2017-07-17T16:01:04+02:00"
23
- },
24
- "Body" : {
25
- "Data" : {
26
- "Site" : {
27
- "Mode" : "meter",
28
- "P_Grid" : -3430.729923,
29
- "P_Load" : -910.270077,
30
- "P_Akku" : null,
31
- "P_PV" : 4341,
32
- "rel_SelfConsumption" : 20.969133,
33
- "rel_Autonomy" : 100,
34
- "E_Day" : 57230,
35
- "E_Year" : 6425915.5,
36
- "E_Total" : 15388710,
37
- "Meter_Location" : "grid"
38
- },
39
- "Inverters" : {
40
- "1" : {
41
- "DT" : 123,
42
- "P" : 4341,
43
- "E_Day" : 57230,
44
- "E_Year" : 6425915.5,
45
- "E_Total" : 15388710
46
- }
47
- }
48
- }
12
+var root = {
13
+ "Body": {
14
+ "Data": {
15
+ "Site": {},
16
+ "Inverters": {}
17
}
50
- };
18
+ }
19
+};
20
52
- beforeAll(function () {
53
- // change this to enable debug log
54
- netdata.options.DEBUG = false;
21
+describe("fronius parsing for power chart", function () {
22
+
23
+ var site = root.Body.Data.Site;
24
+
25
+ afterEach(function () {
26
+ deleteProperties(site);
27
+ });
28
+
29
+ it("should return 3000 for P_Grid when rounded", function () {
30
+ site.P_Grid = 2999.501;
31
+ var result = subject.parsePowerChart(service, site).dimensions[0];
32
+
33
+ expect(result.name).toBe(subject.powerGridId);
34
+ expect(result.value).toBe(3000);
35
+ });
36
+
37
+ it("should return -3000 for P_Grid", function () {
38
+ site.P_Grid = -3000;
39
+ var result = subject.parsePowerChart(service, site).dimensions[0];
40
+
41
+ expect(result.name).toBe(subject.powerGridId);
42
+ expect(result.value).toBe(-3000);
43
+ });
44
+
45
+ it("should return 0 for P_Grid if it is null", function () {
46
+ site.P_Grid = null;
47
+ var result = subject.parsePowerChart(service, site).dimensions[0];
48
+
49
+ expect(result.name).toBe(subject.powerGridId);
50
+ expect(result.value).toBe(0);
51
+ });
52
+
53
+ it("should return 0 for P_Grid if it is zero", function () {
54
+ site.P_Grid = 0;
55
+ var result = subject.parsePowerChart(service, site).dimensions[0];
56
+
57
+ expect(result.name).toBe(subject.powerGridId);
58
+ expect(result.value).toBe(0);
59
+ });
60
+
61
+ it("should return -100 for P_Akku", function () {
62
+ // it is unclear whether negative values are possible for p_akku (couln't test, nor any API docs found).
63
+ site.P_Akku = -100;
64
+ var result = subject.parsePowerChart(service, site).dimensions[2];
65
+
66
+ expect(result.name).toBe(subject.powerAccuId);
67
+ expect(result.value).toBe(-100);
68
+ });
69
+
70
+ it("should return 0 for P_Akku if it is null", function () {
71
+ site.P_Akku = null;
72
+ var result = subject.parsePowerChart(service, site).dimensions[2];
73
+
74
+ expect(result.name).toBe(subject.powerAccuId);
75
+ expect(result.value).toBe(0);
76
+ });
77
+
78
+ it("should return 0 for P_Akku if it is zero", function () {
79
+ site.P_Akku = 0;
80
+ var result = subject.parsePowerChart(service, site).dimensions[2];
81
+
82
+ expect(result.name).toBe(subject.powerAccuId);
83
+ expect(result.value).toBe(0);
84
+ });
85
+
86
+ it("should return 100 for P_PV", function () {
87
+ site.P_PV = 100;
88
+ var result = subject.parsePowerChart(service, site).dimensions[1];
89
+
90
+ expect(result.name).toBe(subject.powerPvId);
91
+ expect(result.value).toBe(100);
92
+ });
93
+
94
+ it("should return 0 for P_PV if it is zero", function () {
95
+ site.P_PV = 0;
96
+ var result = subject.parsePowerChart(service, site).dimensions[1];
97
+
98
+ expect(result.name).toBe(subject.powerPvId);
99
+ expect(result.value).toBe(0);
100
+ });
101
+
102
+ it("should return 0 for P_PV if it is null", function () {
103
+ site.P_PV = null;
104
+ var result = subject.parsePowerChart(service, site).dimensions[1];
105
+
106
+ expect(result.name).toBe(subject.powerPvId);
107
+ expect(result.value).toBe(0);
108
+ });
109
+
110
+ it("should return 0 for P_PV if it is negative", function () {
111
+ // solar panels shouldn't consume anything, only produce.
112
+ site.P_PV = -1;
113
+ var result = subject.parsePowerChart(service, site).dimensions[1];
114
+
115
+ expect(result.name).toBe(subject.powerPvId);
116
+ expect(result.value).toBe(0);
117
+ });
118
+
119
+});
120
+
121
+describe("fronius parsing for consumption", function () {
122
+
123
+ var site = root.Body.Data.Site;
124
+
125
+ afterEach(function () {
126
+ deleteProperties(site);
127
+ });
128
+
129
+ it("should return 1000 for P_Load when rounded", function () {
130
+ site.P_Load = 1000.499;
131
+ var result = subject.parseConsumptionChart(service, site).dimensions[0];
132
+
133
+ expect(result.name).toBe(subject.consumptionLoadId);
134
+ expect(result.value).toBe(1000);
135
+ });
136
+
137
+ it("should return absolute value for P_Load when negative", function () {
138
+ /*
139
+ with firmware 3.7.4 it is sometimes possible that negative values are returned for P_Load,
140
+ which makes absolutely no sense. There is always a device that consumes some electricity around the clock.
141
+ Best we can do is to make it a positive value, since 0 also doesn't make much sense.
142
+ This "workaround" seems to work, as there couldn't be any strange peaks observed during long-time testing.
143
+ */
144
+ site.P_Load = -50;
145
+ var result = subject.parseConsumptionChart(service, site).dimensions[0];
146
+
147
+ expect(result.name).toBe(subject.consumptionLoadId);
148
+ expect(result.value).toBe(50);
149
+ });
150
+
151
+ it("should return 0 for P_Load if it is null", function () {
152
+ site.P_Load = null;
153
+ var result = subject.parseConsumptionChart(service, site).dimensions[0];
154
+
155
+ expect(result.name).toBe(subject.consumptionLoadId);
156
+ expect(result.value).toBe(0);
157
+ });
158
+
159
+ it("should return 0 for P_Load if it is zero", function () {
160
+ site.P_Load = 0;
161
+ var result = subject.parseConsumptionChart(service, site).dimensions[0];
162
+
163
+ expect(result.name).toBe(subject.consumptionLoadId);
164
+ expect(result.value).toBe(0);
165
+ });
166
+
167
+});
168
+
169
+describe("fronius parsing for autonomy", function () {
170
+
171
+ var site = root.Body.Data.Site;
172
+
173
+ afterEach(function () {
174
+ deleteProperties(site);
175
+ });
176
+
177
+ it("should return 100 for rel_Autonomy", function () {
178
+ site.rel_Autonomy = 100;
179
+ var result = subject.parseAutonomyChart(service, site).dimensions[0];
180
+
181
+ expect(result.name).toBe(subject.autonomyId);
182
+ expect(result.value).toBe(100);
183
+ });
184
+
185
+ it("should return 0 for rel_Autonomy if it is zero", function () {
186
+ site.rel_Autonomy = 0;
187
+ var result = subject.parseAutonomyChart(service, site).dimensions[0];
188
+
189
+ expect(result.name).toBe(subject.autonomyId);
190
+ expect(result.value).toBe(0);
191
});
192
57
- it("should return a parsed value", function () {
58
- // arrange
59
- netdata.send = jasmine.createSpy("send");
60
- // act
61
- subject.processResponse(service, fakeResponse);
62
- var result = netdata.send.calls.argsFor(0)[0];
63
- // assert
64
- expect(result).toContain("SET p_grid = -3431");
193
+ it("should return 0 for rel_Autonomy if it is null", function () {
194
+ site.rel_Autonomy = null;
195
+ var result = subject.parseAutonomyChart(service, site).dimensions[0];
196
+
197
+ expect(result.name).toBe(subject.autonomyId);
198
+ expect(result.value).toBe(0);
199
});
200
201
+ it("should return 20 for rel_Autonomy if it is 20", function () {
202
+ site.rel_Autonomy = 20.1;
203
+ var result = subject.parseAutonomyChart(service, site).dimensions[0];
204
+
205
+ expect(result.name).toBe(subject.autonomyId);
206
+ expect(result.value).toBe(20);
207
+ });
208
+
209
+ it("should return 20 for rel_SelfConsumption if it is 19.5", function () {
210
+ site.rel_SelfConsumption = 19.5;
211
+ var result = subject.parseAutonomyChart(service, site).dimensions[1];
212
+
213
+ expect(result.name).toBe(subject.consumptionSelfId);
214
+ expect(result.value).toBe(20);
215
+ });
216
+
217
+ it("should return 100 for rel_SelfConsumption if it is null", function () {
218
+ /*
219
+ During testing it could be observed that the API is delivering null if the solar panels
220
+ do not produce enough energy to supply the local load. But in this case it should be 100, since all
221
+ the produced energy is directly consumed.
222
+ */
223
+ site.rel_SelfConsumption = null;
224
+ var result = subject.parseAutonomyChart(service, site).dimensions[1];
225
+
226
+ expect(result.name).toBe(subject.consumptionSelfId);
227
+ expect(result.value).toBe(100);
228
+ });
229
+
230
+ it("should return 0 for rel_SelfConsumption if it is zero", function () {
231
+ site.rel_SelfConsumption = 0;
232
+ var result = subject.parseAutonomyChart(service, site).dimensions[1];
233
+
234
+ expect(result.name).toBe(subject.consumptionSelfId);
235
+ expect(result.value).toBe(0);
236
+ });
237
});
238
+
239
+describe("fronius parsing for energy", function () {
240
+
241
+ var site = root.Body.Data.Site;
242
+
243
+ afterEach(function () {
244
+ deleteProperties(site);
245
+ });
246
+
247
+ it("should return 10000 for E_Day", function () {
248
+ site.E_Day = 10000;
249
+ var result = subject.parseEnergyTodayChart(service, site).dimensions[0];
250
+
251
+ expect(result.name).toBe(subject.energyTodayId);
252
+ expect(result.value).toBe(10000);
253
+ });
254
+
255
+ it("should return 0 for E_Day if it is negative", function () {
256
+ /*
257
+ The solar panels can't produce negative energy, really. It would be a fault of the API.
258
+ */
259
+ site.E_Day = -0.4;
260
+ var result = subject.parseEnergyTodayChart(service, site).dimensions[0];
261
+
262
+ expect(result.name).toBe(subject.energyTodayId);
263
+ expect(result.value).toBe(0);
264
+ });
265
+
266
+ it("should return 100'000 for E_Year", function () {
267
+ site.E_Year = 100000.4;
268
+ var result = subject.parseEnergyYearChart(service, site).dimensions[0];
269
+
270
+ expect(result.name).toBe(subject.energyYearId);
271
+ expect(result.value).toBe(100000);
272
+ });
273
+
274
+ it("should return 0 for E_Year if it is negative", function () {
275
+ /*
276
+ A return value of 0 only makes sense in the silvester night anyway, when the counter is being reset.
277
+ A negative value is a fault from the API though. It wouldn't make sense.
278
+ */
279
+ site.E_Year = -1;
280
+ var result = subject.parseEnergyYearChart(service, site).dimensions[0];
281
+
282
+ expect(result.name).toBe(subject.energyYearId);
283
+ expect(result.value).toBe(0);
284
+ });
285
+});
286
+
287
+describe("fronius parsing for inverters", function () {
288
+
289
+ var inverters = root.Body.Data.Inverters;
290
+
291
+ afterEach(function () {
292
+ deleteProperties(inverters);
293
+ });
294
+
295
+ it("should return 1000 for P for inverter with name", function () {
296
+ inverters["cellar"] = {
297
+ P: 1000
298
+ };
299
+ var result = subject.parseInverterChart(service, inverters).dimensions[0];
300
+
301
+ expect(result.name).toBe("cellar");
302
+ expect(result.value).toBe(1000);
303
+ });
304
+
305
+});
\ No newline at end of file
tests/node.d/fronius.process.spec.js
new
+74
@@ -0,0 +1,74 @@
1
+"use strict";
2
+
3
+var netdata = require("../../node.d/node_modules/netdata");
4
+// remember: subject will be a singleton!
5
+var subject = require("../../node.d/fronius.node");
6
+
7
+var service = netdata.service({
8
+ name: "process",
9
+ module: this
10
+});
11
+
12
+var exampleResponse = {
13
+ "Body": {
14
+ "Data": {
15
+ "Site": {
16
+ "Mode": "meter",
17
+ "P_Grid": -3430.729923,
18
+ "P_Load": -910.270077,
19
+ "P_Akku": null,
20
+ "P_PV": 4341,
21
+ "rel_SelfConsumption": 20.969133,
22
+ "rel_Autonomy": 100,
23
+ "E_Day": 57230,
24
+ "E_Year": 6425915.5,
25
+ "E_Total": 15388710,
26
+ "Meter_Location": "grid"
27
+ },
28
+ "Inverters": {
29
+ "1": {
30
+ "DT": 123,
31
+ "P": 4341,
32
+ "E_Day": 57230,
33
+ "E_Year": 6425915.5,
34
+ "E_Total": 15388710
35
+ }
36
+ }
37
+ }
38
+ }
39
+};
40
+
41
+describe("fronius main processing", function () {
42
+
43
+ beforeAll(function () {
44
+ // change this to enable debug log
45
+ netdata.options.DEBUG = false;
46
+ });
47
+
48
+ beforeEach(function () {
49
+ deleteProperties(subject.charts);
50
+ });
51
+
52
+ it("should send parsed values to netdata", function () {
53
+ netdata.send = jasmine.createSpy("send");
54
+
55
+ subject.processResponse(service, exampleResponse);
56
+
57
+ expect(netdata.send.calls.count()).toBe(6);
58
+
59
+ // check if some parsed values were sent.
60
+ var powerChart = netdata.send.calls.argsFor(5)[0];
61
+
62
+ expect(powerChart).toContain("SET p_grid = -3431");
63
+ expect(powerChart).toContain("SET p_pv = 4341");
64
+
65
+ var inverterChart = netdata.send.calls.argsFor(0)[0];
66
+
67
+ expect(inverterChart).toContain("SET 1 = 4341");
68
+
69
+ var autonomyChart = netdata.send.calls.argsFor(3)[0];
70
+ expect(autonomyChart).toContain("SET rel_selfconsumption = 21");
71
+ });
72
+
73
+
74
+});
\ No newline at end of file
tests/node.d/fronius.validation.spec.js
+12
-16
@@ -5,26 +5,12 @@ var netdata = require("../../node.d/node_modules/netdata");
5
var subject = require("../../node.d/fronius.node");
6
7
var service = netdata.service({
8
- name: "fronius",
8
+ name: "validation",
9
module: this
10
});
11
12
describe("fronius response validation", function () {
13
14
- // this is a faked JSON response from the server.
15
- // Used with freeformatter.com/json-escape.html to escape the json and turn it into a string.
16
- var fakeResponse = "{\r\n\t\"Head\" : {\r\n\t\t\"RequestArguments\" : {},\r\n\t\t\"Status\" " +
17
- ": {\r\n\t\t\t\"Code\" : 0,\r\n\t\t\t\"Reason\" : \"\",\r\n\t\t\t\"UserMessage\" : " +
18
- "\"\"\r\n\t\t},\r\n\t\t\"Timestamp\" : \"2017-07-17T16:01:04+02:00\"\r\n\t},\r\n\t\"Body\" : " +
19
- "{\r\n\t\t\"Data\" : {\r\n\t\t\t\"Site\" : {\r\n\t\t\t\t\"Mode\" : \"meter\",\r\n\t\t\t\t\"P_Grid\" " +
20
- ": -3430.729923,\r\n\t\t\t\t\"P_Load\" : -910.270077,\r\n\t\t\t\t\"P_Akku\" : " +
21
- "null,\r\n\t\t\t\t\"P_PV\" : 4341,\r\n\t\t\t\t\"rel_SelfConsumption\" : " +
22
- "20.969133,\r\n\t\t\t\t\"rel_Autonomy\" : 100,\r\n\t\t\t\t\"E_Day\" : 57230,\r\n\t\t\t\t\"E_Year\" " +
23
- ": 6425915.5,\r\n\t\t\t\t\"E_Total\" : 15388710,\r\n\t\t\t\t\"Meter_Location\" : " +
24
- "\"grid\"\r\n\t\t\t},\r\n\t\t\t\"Inverters\" : {\r\n\t\t\t\t\"1\" : {\r\n\t\t\t\t\t\"DT\" : " +
25
- "123,\r\n\t\t\t\t\t\"P\" : 4341,\r\n\t\t\t\t\t\"E_Day\" : 57230,\r\n\t\t\t\t\t\"E_Year\" : " +
26
- "6425915.5,\r\n\t\t\t\t\t\"E_Total\" : 15388710\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}";
27
-
14
it("should do nothing if response is null", function () {
15
netdata.send = jasmine.createSpy("send");
16
@@ -35,9 +21,19 @@ describe("fronius response validation", function () {
21
});
22
23
it("should return null if response is null", function () {
38
- var result = subject.parseResponse(null);
24
+ var result = subject.convertToJson(null);
25
+
26
+ expect(result).toBeNull();
27
+ });
28
+
29
+ it("should return null and log error if response cannot be parsed", function () {
30
+ netdata.error = jasmine.createSpy("error");
31
+
32
+ // trailing commas are enough to create syntax exceptions
33
+ var result = subject.convertToJson("{name,}");
34
35
expect(result).toBeNull();
36
+ expect(netdata.error.calls.count()).toBe(1);
37
});
38
39
it("should return true if response is valid", function () {