]> arthur.barton.de Git - netdata.git/blob - plugins.d/sma_webbox.plugin
53007247fa3041b4ed54a777bc8afdab14bb6627
[netdata.git] / plugins.d / sma_webbox.plugin
1 #!/usr/bin/env node
2
3 // This program will connect to one or more SMA Sunny Webboxes
4 // to get the Solar Power Generated (current, today, total).
5
6 // It needs a configuration file named sma_webbox.conf
7 // The configuration is a JSON array, like this:
8
9 /* --- BEGIN EXAMPLE CONFIGURATION ---
10
11 [
12         {
13                 "name": "label_of_solar_installation1",
14                 "hostname": "10.11.13.2"
15         },
16         {
17                 "name": "label_of_solar_installation2",
18                 "hostname": "10.11.13.3"
19         }
20 ]
21
22    --- END EXAMPLE CONFIGURATION --- */
23
24 // you can add an unlimited number of webboxes
25
26 var http = require('http');
27 var fs = require('fs');
28
29 // --------------------------------------------------------------------------------------------------------------------
30 // get NETDATA environment variables
31
32 var NETDATA_PLUGINS_DIR = process.env.NETDATA_PLUGINS_DIR || __dirname;
33 var NETDATA_CONFIG_DIR = process.env.NETDATA_CONFIG_DIR || '/etc/netdata';
34 var NETDATA_UPDATE_EVERY = process.env.NETDATA_UPDATE_EVERY || 1;
35 NETDATA_UPDATE_EVERY = NETDATA_UPDATE_EVERY * 1000;
36
37 var filename = NETDATA_CONFIG_DIR + '/sma_webbox.conf';
38
39 // --------------------------------------------------------------------------------------------------------------------
40 // get command line arguments
41
42 var DEBUG = false;
43 var UPDATE_EVERY = NETDATA_UPDATE_EVERY;
44 var EXIT_AFTER_MS = 3600 * 1000;
45
46 function debug(msg) {
47         if(DEBUG)
48                 console.error(__filename + ': DEBUG: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
49 }
50
51 function error(msg) {
52         console.error(__filename + ': ERROR: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
53 }
54
55 function netdata(msg) {
56         console.log(msg.toString());
57 }
58
59 var found_myself = false;
60 process.argv.forEach(function (val, index, array) {
61         debug('PARAM: ' + val);
62
63         if(!found_myself) {
64                 if(val === __filename)
65                         found_myself = true;
66         }
67         else {
68                 switch(val) {
69                         case 'debug':
70                                 DEBUG = true;
71                                 debug('DEBUG enabled');
72                                 break;
73
74                         default:
75                                 try {
76                                         var x = parseInt(val);
77                                         if(x > 0) {
78                                                 UPDATE_EVERY = x * 1000;
79                                                 if(UPDATE_EVERY < NETDATA_UPDATE_EVERY) {
80                                                         UPDATE_EVERY = NETDATA_UPDATE_EVERY;
81                                                         debug('Update frequency ' + x + 's is too low');
82                                                 }
83
84                                                 debug('Update frequency set to ' + UPDATE_EVERY + ' ms');
85                                         }
86                                         else error('Ignoring parameter: ' + val);
87                                 }
88                                 catch(e) {
89                                         error('Cannot get value of parameter: ' + val);
90                                 }
91                 }
92         }
93 });
94
95 // there is no meaning to update the values sooner than 5 seconds
96 // the SMA webbox collects data every 5 seconds
97 if(UPDATE_EVERY < 5000) {
98         debug('Adjusting update frequency to 5 seconds');
99         UPDATE_EVERY = 5000;
100 }
101
102 // --------------------------------------------------------------------------------------------------------------------
103 // parse configuration
104
105 var Servers = new Array();
106 try {
107         Servers = JSON.parse(fs.readFileSync(filename, 'utf8'));
108 }
109 catch(e) {
110         error('Cannot read configuration file ' + filename + ': ' + e.message);
111         process.exit(1);
112 }
113
114
115 // --------------------------------------------------------------------------------------------------------------------
116 // library functions
117
118 // function to get data from server
119 function getSMAData(server, callback) {
120         // make sure there is not a request in progress for this webbox
121         if(typeof(server.running) === 'boolean' && server.running) {
122                 debug(server.name + ' is already running');
123                 return false;
124         }
125         server.running = true;
126
127         var now = new Date().getTime();
128
129         // align the time to collect the values
130         if(typeof(server.next_run) === 'undefined')
131                 server.next_run = now - (now % UPDATE_EVERY) + UPDATE_EVERY;
132
133         // if it is too soon, we will collect data later
134         if(now < server.next_run) {
135                 debug(server.name + ' not yet');
136                 server.running = false;
137                 return false;
138         }
139
140         // find the next refresh for this server
141         while(server.next_run < now)
142                 server.next_run += UPDATE_EVERY;
143
144         if(typeof(server.last_updated) === 'undefined')
145                 server.last_updated = 0;
146
147         if(typeof(server.agent) === 'undefined') {
148                 server.agent = new http.Agent({
149                         keepAlive: true,
150                         keepAliveMsecs: UPDATE_EVERY * 5,
151                         maxSockets: 2, // it needs 2, otherwise it ignores keepAlive
152                         maxFreeSockets: 1
153                 });
154         }
155
156         if(typeof(server.created) === 'undefined') {
157                 netdata('CHART sma_webbox_' + server.name + '.current "" "Solar Power Production of ' + server.name + '" "Watts" sma_webbox_' + server.name + ' "" area 15000 ' + Math.round(UPDATE_EVERY / 1000));
158                 netdata('DIMENSION GriPwr power absolute 1 1');
159                 netdata('CHART sma_webbox_' + server.name + '.today "" "Today\'s Solar Power Production of ' + server.name + '" "kWatts" sma_webbox_' + server.name + ' "" area 15001 ' + Math.round(UPDATE_EVERY / 1000));
160                 netdata('DIMENSION GriEgyTdy today absolute 1 1000');
161                 netdata('CHART sma_webbox_' + server.name + '.total "" "Total Solar Power Production of ' + server.name + '" "kWatts" sma_webbox_' + server.name + ' "" area 15001 ' + Math.round(UPDATE_EVERY / 1000));
162                 netdata('DIMENSION GriEgyTot total absolute 1 1000');
163                 server.created = true;
164         }
165
166         server.error = 500;
167         server.success = false;
168         server.data = {
169                 'GriPwr': {
170                         unit: null,
171                         value: null
172                 },
173                 'GriEgyTdy': {
174                         unit: null,
175                         value: null
176                 },
177                 'GriEgyTot': {
178                         unit: null,
179                         value: null
180                 }
181         };
182
183         var postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}'
184
185         var options = {
186                 protocol: server.protocol || 'http:',
187                 hostname: server.hostname,
188                 path: server.path || '/rpc',
189                 family: server.family || 4,
190                 port: server.port || 80,
191                 method: 'POST',
192                 headers: {
193                         'Content-Type': 'application/x-www-form-urlencoded',
194                         'Connection': 'keep-alive',
195                         'Content-Length': postData.length
196                 },
197                 agent: server.agent
198         };
199
200         server.response = '';
201         server.request = http.request(options, function(res) {
202                 res.setEncoding('utf8');
203                 server.error = 0;
204
205                 server.response_code = res.statusCode;
206
207                 // check if we got HTTP/200
208                 if(server.response_code !== 200) {
209                         debug('Server responded with ' + server.response_code + ', failed to get data.');
210                         server.error = 503;
211                         server.running = false;
212                         server.success = false;
213                         if(typeof(callback) === 'function')
214                                 callback(server);
215                 }
216
217                 res.on('error', function() {
218                         debug('Received HTTP read error, failed to get data.');
219                         server.error = 504;
220                         server.success = false;
221                         server.running = false;
222                         if(typeof(callback) === 'function')
223                                 callback(server);
224                 });
225
226                 res.on('data', function(chunk) {
227                         server.response += chunk;
228                 });
229
230                 res.on('end', function() {
231                         if(server.error !== 0)
232                                 return;
233
234                         var t = new Date().getTime();
235
236                         server.dt = 0;
237                         if(server.last_updated !== 0)
238                                 server.dt = t - server.last_updated;
239
240                         debug('RESPONSE: ' + server.response);
241                         try {
242                                 server.response = JSON.parse(server.response);
243
244                                 var len = server.response.result.overview.length;
245                                 while(len--) {
246                                         var e = server.response.result.overview[len];
247                                         debug(e);
248                                         if(typeof(server.data[e.meta]) !== 'undefined') {
249                                                 server.data[e.meta].value = e.value;
250                                                 server.data[e.meta].unit = e.unit;
251                                         }
252                                 }
253                                 server.error = 0;
254                                 server.last_updated = t;
255
256                                 if(server.data['GriPwr'].value !== null) {
257                                         netdata('BEGIN sma_webbox_' + server.name + '.current ' + ((server.dt)?server.dt*1000:'').toString());
258                                         netdata('SET GriPwr = ' + server.data['GriPwr'].value);
259                                         netdata('END');
260                                 }
261
262                                 if(server.data['GriEgyTdy'].value !== null) {
263                                         netdata('BEGIN sma_webbox_' + server.name + '.today ' + ((server.dt)?server.dt*1000:'').toString());
264                                         netdata('SET GriEgyTdy = ' + Math.round(server.data['GriEgyTdy'].value * 1000));
265                                         netdata('END');
266                                 }
267
268                                 if(server.data['GriEgyTot'].value !== null) {
269                                         netdata('BEGIN sma_webbox_' + server.name + '.total ' + ((server.dt)?server.dt*1000:'').toString());
270                                         netdata('SET GriEgyTot = ' + Math.round(server.data['GriEgyTot'].value * 1000));
271                                         netdata('END');
272                                 }
273                         }
274                         catch(e) {
275                                 server.error = 501;
276                                 server.failure_message = e.message;
277                                 error('FAILED TO PARSE DATA: ' + e.message);
278                         }
279
280                         server.running = false;
281                         if(typeof(callback) === 'function') {
282                                 server.success = true;
283                                 callback(server);
284                         }
285                 });
286         });
287
288         server.request.on('error', function(e) {
289                 server.running = false;
290                 server.error = 502;
291                 server.failure_message = e.message;
292                 error('problem with request to ' + server.hostname + ': ' + e.message);
293         });
294
295         // write data to request body
296         server.request.write(postData);
297         server.request.end();
298
299         return true;
300 }
301
302 // get a message for the current error
303 function getSMAError(server) {
304         if(server.success) return 'OK';
305         
306         if(typeof(server.error) === 'undefined')
307                 return 'Not initialized';
308
309         switch(server.error) {
310                 case 500: return 'Pre-fetch Error';
311                 case 501: return 'Failed to parse server response';
312                 case 502: return 'HTTP error while making request';
313                 case 504: return 'Failed to read response data';
314                 default: return 'Undefined Error';
315         }
316 }
317
318 // --------------------------------------------------------------------------------------------------------------------
319
320 // if we don't have any servers to run
321 // inform netdata we don't need to run on this machine
322 if(!Servers.length) {
323         error('No SMA webbox servers defined.');
324         process.exit(1);
325 }
326
327 var started = new Date().getTime();
328 function runServers() {
329         var now = new Date().getTime();
330         if(now - started > EXIT_AFTER_MS) {
331                 debug('We have to exit now. Netdata will restart us.');
332                 // FIXME
333                 // We should wait for any currently running requests to complete
334                 process.exit(0);
335         }
336
337         var len = Servers.length;
338         while(len--) {
339                 getSMAData(Servers[len], function(server) {
340                         if(server.success)
341                                 debug(' OK ' + server.name + ' time since last run: ' + server.dt + ' ms');
342                         else
343                                 error(getSMAError(server) + server.name);
344                 });
345         }
346
347         setTimeout(runServers, 100);
348 }
349
350 runServers();