fronius.node.js 14 KB

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