3 // This program will connect to one or more SMA Sunny Webboxes
4 // to get the Solar Power Generated (current, today, total).
6 // It needs a configuration file named sma_webbox.conf
7 // The configuration is a JSON array, like this:
9 /* --- BEGIN EXAMPLE CONFIGURATION ---
13 "name": "label_of_solar_installation1",
14 "hostname": "10.11.13.2"
17 "name": "label_of_solar_installation2",
18 "hostname": "10.11.13.3"
22 --- END EXAMPLE CONFIGURATION --- */
24 // you can add an unlimited number of webboxes
26 var http = require('http');
27 var fs = require('fs');
29 // --------------------------------------------------------------------------------------------------------------------
30 // get NETDATA environment variables
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;
37 var filename = NETDATA_CONFIG_DIR + '/sma_webbox.conf';
39 // --------------------------------------------------------------------------------------------------------------------
40 // get command line arguments
43 var UPDATE_EVERY = NETDATA_UPDATE_EVERY;
44 var EXIT_AFTER_MS = 3600 * 1000;
49 console.error(now.toString() + ': ' + __filename + ': DEBUG: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
55 console.error(now.toString() + ': ' + __filename + ': ERROR: ' + ((typeof(msg) === 'object')?JSON.stringify(msg):msg).toString());
58 function netdata(msg) {
59 console.log(msg.toString());
62 var found_myself = false;
63 process.argv.forEach(function (val, index, array) {
64 debug('PARAM: ' + val);
67 if(val === __filename)
74 debug('DEBUG enabled');
79 var x = parseInt(val);
81 UPDATE_EVERY = x * 1000;
82 if(UPDATE_EVERY < NETDATA_UPDATE_EVERY) {
83 UPDATE_EVERY = NETDATA_UPDATE_EVERY;
84 debug('Update frequency ' + x + 's is too low');
87 debug('Update frequency set to ' + UPDATE_EVERY + ' ms');
89 else error('Ignoring parameter: ' + val);
92 error('Cannot get value of parameter: ' + val);
98 // there is no meaning to update the values sooner than 5 seconds
99 // the SMA webbox collects data every 5 seconds
100 if(UPDATE_EVERY < 5000) {
101 debug('Adjusting update frequency to 5 seconds');
105 // --------------------------------------------------------------------------------------------------------------------
106 // parse configuration
108 var Servers = new Array();
110 Servers = JSON.parse(fs.readFileSync(filename, 'utf8'));
113 error('Cannot read configuration file ' + filename + ': ' + e.message);
119 // --------------------------------------------------------------------------------------------------------------------
122 // function to get data from server
123 function getSMAData(server, callback) {
124 // make sure there is not a request in progress for this webbox
125 if(typeof(server.running) === 'boolean' && server.running) {
126 debug(server.name + ' is already running');
129 server.running = true;
131 var now = new Date().getTime();
133 // align the time to collect the values
134 if(typeof(server.next_run) === 'undefined')
135 server.next_run = now - (now % UPDATE_EVERY) + UPDATE_EVERY;
137 // if it is too soon, we will collect data later
138 if(now < server.next_run) {
139 debug(server.name + ' not yet');
140 server.running = false;
144 // find the next refresh for this server
145 while(server.next_run < now)
146 server.next_run += UPDATE_EVERY;
148 if(typeof(server.last_updated) === 'undefined')
149 server.last_updated = 0;
151 // create an agent for keep-alive
152 if(typeof(server.agent) === 'undefined') {
153 server.agent = new http.Agent({
155 keepAliveMsecs: UPDATE_EVERY * 5,
156 maxSockets: 2, // it needs 2, otherwise it ignores keepAlive
161 // create the charts, if we haven't already
162 if(typeof(server.created) === 'undefined') {
163 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));
164 netdata('DIMENSION GriPwr power absolute 1 1');
166 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));
167 netdata('DIMENSION GriEgyTdy today absolute 1 1000');
169 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));
170 netdata('DIMENSION GriEgyTot total absolute 1 1000');
172 server.created = true;
175 // initialize our metrics to null
176 // so that we will know if we read them or not
192 var postData = 'RPC={"proc":"GetPlantOverview","format":"JSON","version":"1.0","id":"1"}'
195 protocol: server.protocol || 'http:',
196 hostname: server.hostname,
197 path: server.path || '/rpc',
198 family: server.family || 4,
199 port: server.port || 80,
202 'Content-Type': 'application/x-www-form-urlencoded',
203 'Connection': 'keep-alive',
204 'Content-Length': postData.length
210 server.success = false;
211 server.response = '';
212 server.request = http.request(options, function(res) {
213 res.setEncoding('utf8');
214 server.response_code = res.statusCode;
216 // check if we got HTTP/200
217 if(server.response_code !== 200) {
218 // if this request is in error and we have
219 // handled it, don't do anything more
220 if(server.error === 0) {
221 debug('Server responded with ' + server.response_code + ', failed to get data.');
223 server.success = false;
224 server.running = false;
225 if(typeof(callback) === 'function')
230 res.on('data', function(chunk) {
231 // read more data, only if there is no error
232 if(server.error === 0)
233 server.response += chunk;
236 res.on('error', function() {
237 // do we have already handled this error?
238 if(server.error !== 0)
241 debug('Received HTTP read error, failed to get data.');
243 server.success = false;
244 server.running = false;
245 if(typeof(callback) === 'function')
249 res.on('end', function() {
250 // if there is an error we have handled
252 if(server.error !== 0)
255 var t = new Date().getTime();
258 if(server.last_updated !== 0)
259 server.dt = t - server.last_updated;
261 debug('RESPONSE: ' + server.response);
263 server.response = JSON.parse(server.response);
265 var len = server.response.result.overview.length;
267 var e = server.response.result.overview[len];
269 if(typeof(server.data[e.meta]) !== 'undefined') {
270 server.data[e.meta].value = e.value;
271 server.data[e.meta].unit = e.unit;
275 if(server.data['GriPwr'].value !== null) {
276 netdata('BEGIN sma_webbox_' + server.name + '.current ' + ((server.dt)?server.dt*1000:'').toString());
277 netdata('SET GriPwr = ' + server.data['GriPwr'].value);
281 if(server.data['GriEgyTdy'].value !== null) {
282 netdata('BEGIN sma_webbox_' + server.name + '.today ' + ((server.dt)?server.dt*1000:'').toString());
283 netdata('SET GriEgyTdy = ' + Math.round(server.data['GriEgyTdy'].value * 1000));
287 if(server.data['GriEgyTot'].value !== null) {
288 netdata('BEGIN sma_webbox_' + server.name + '.total ' + ((server.dt)?server.dt*1000:'').toString());
289 netdata('SET GriEgyTot = ' + Math.round(server.data['GriEgyTot'].value * 1000));
294 server.success = true;
295 server.last_updated = t;
299 server.success = false;
300 server.failure_message = e.message;
301 error('FAILED TO PARSE DATA: ' + e.message);
304 server.running = false;
305 if(typeof(callback) === 'function')
310 server.request.on('error', function(e) {
311 // do we have already handled this error?
312 if(server.error !== 0)
316 server.success = false;
317 server.running = false;
318 server.failure_message = e.message;
319 error('problem with request to ' + server.hostname + ': ' + e.message);
321 if(typeof(callback) === 'function')
325 // write data to request body
326 server.request.write(postData);
327 server.request.end();
332 // get a message for the current error
333 function getSMAError(server) {
334 if(server.success) return 'OK';
336 if(typeof(server.error) === 'undefined')
337 return 'Not initialized';
339 switch(server.error) {
340 case 500: return 'Pre-fetch Error';
341 case 501: return 'Failed to parse server response';
342 case 502: return 'HTTP error while making request';
343 case 504: return 'Failed to read response data';
344 default: return 'Undefined Error';
348 // --------------------------------------------------------------------------------------------------------------------
350 // if we don't have any servers to run
351 // inform netdata we don't need to run on this machine
352 if(!Servers.length) {
353 error('No SMA webbox servers defined.');
358 var started = new Date().getTime();
359 function runServers() {
360 var now = new Date().getTime();
361 if(now - started > EXIT_AFTER_MS) {
362 debug('We have to exit now. Netdata will restart us.');
364 // We should wait for any currently running requests to complete
368 var len = Servers.length;
370 getSMAData(Servers[len], function(server) {
372 debug(' OK ' + server.name + ' time since last run: ' + server.dt + ' ms');
374 error(getSMAError(server) + server.name);
378 setTimeout(runServers, 100);