diff options
Diffstat (limited to 'collectors/node.d.plugin/stiebeleltron')
-rw-r--r-- | collectors/node.d.plugin/stiebeleltron/Makefile.inc | 13 | ||||
-rw-r--r-- | collectors/node.d.plugin/stiebeleltron/README.md | 505 | ||||
-rw-r--r-- | collectors/node.d.plugin/stiebeleltron/stiebeleltron.node.js | 197 |
3 files changed, 715 insertions, 0 deletions
diff --git a/collectors/node.d.plugin/stiebeleltron/Makefile.inc b/collectors/node.d.plugin/stiebeleltron/Makefile.inc new file mode 100644 index 000000000..0c6e1e213 --- /dev/null +++ b/collectors/node.d.plugin/stiebeleltron/Makefile.inc @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# THIS IS NOT A COMPLETE Makefile +# IT IS INCLUDED BY ITS PARENT'S Makefile.am +# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT + +# install these files +dist_node_DATA += stiebeleltron/stiebeleltron.node.js +# dist_nodeconfig_DATA += stiebeleltron/stiebeleltron.conf + +# do not install these files, but include them in the distribution +dist_noinst_DATA += stiebeleltron/README.md stiebeleltron/Makefile.inc + diff --git a/collectors/node.d.plugin/stiebeleltron/README.md b/collectors/node.d.plugin/stiebeleltron/README.md new file mode 100644 index 000000000..002a31571 --- /dev/null +++ b/collectors/node.d.plugin/stiebeleltron/README.md @@ -0,0 +1,505 @@ +# stiebel eltron + +This module collects metrics from the configured heat pump and hot water installation from Stiebel Eltron ISG web. + +**Requirements** + * Configuration file `stiebeleltron.conf` in the node.d netdata config dir (default: `/etc/netdata/node.d/stiebeleltron.conf`) + * Stiebel Eltron ISG web with network access (http), without password login + +The charts are configurable, however, the provided default configuration collects the following: + +1. **General** + * Outside temperature in C + * Condenser temperature in C + * Heating circuit pressure in bar + * Flow rate in l/min + * Output of water and heat pumps in % + +2. **Heating** + * Heat circuit 1 temperature in C (set/actual) + * Heat circuit 2 temperature in C (set/actual) + * Flow temperature in C (set/actual) + * Buffer temperature in C (set/actual) + * Pre-flow temperature in C + +3. **Hot Water** + * Hot water temperature in C (set/actual) + +4. **Room Temperature** + * Heat circuit 1 room temperature in C (set/actual) + * Heat circuit 2 room temperature in C (set/actual) + +5. **Eletric Reheating** + * Dual Mode Reheating temperature in C (hot water/heating) + +6. **Process Data** + * Remaining compressor rest time in s + +7. **Runtime** + * Compressor runtime hours (hot water/heating) + * Reheating runtime hours (reheating 1/reheating 2) + +8. **Energy** + * Compressor today in kWh (hot water/heating) + * Compressor Total in kWh (hot water/heating) + + +### configuration + +If no configuration is given, the module will be disabled. Each `update_every` is optional, the default is `10`. + +--- + +[Stiebel Eltron Heat pump system with ISG](https://www.stiebel-eltron.com/en/home/products-solutions/renewables/controller_energymanagement/internet_servicegateway/isg_web.html) + +Original author: BrainDoctor (github) + +The module supports any metrics that are parseable with RegEx. There is no API that gives direct access to the values (AFAIK), so the "workaround" is to parse the HTML output of the ISG. + +### Testing +This plugin has been tested within the following environment: + * ISG version: 8.5.6 + * MFG version: 12 + * Controller version: 9 + * July (summer time, not much activity) + * Interface language: English + * login- and password-less ISG web access (without HTTPS it's useless anyway) + * Heatpump model: WPL 25 I-2 + * Hot water boiler model: 820 WT 1 + +So, if the language is set to english, copy the following configuration into `/etc/netdata/node.d/stiebeleltron.conf` and change the `url`s. + +In my case, the ISG is relatively slow with responding (at least 1s, but also up to 4s). Collecting metrics every 10s is more than enough for me. + +### How to update the config + +* The dimensions support variable digits, the default is `1`. Most of the values printed by ISG are using 1 digit, some use 2. +* The dimensions also support the `multiplier` and `divisor` attributes, however the divisor gets overridden by `digits`, if specified. Default is `1`. +* The test string for the regex is always the whole HTML output from the url. For each parameter you need to have a regular expression that extracts the value from the HTML source in the first capture group. + Recommended: [regexr.com](https://regexr.com/) for testing and matching, [freeformatter.com](https://www.freeformatter.com/json-escape.html) for escaping the newly created regex for the JSON config. + +The charts are being generated using the configuration below. So if your installation is in another language or has other metrics, just adapt the structure or regexes. +### Configuration template +```json +{ + "enable_autodetect": false, + "update_every": 10, + "pages": [ + { + "name": "System", + "id": "system", + "url": "http://machine.ip.or.dns/?s=1,0", + "update_every": 10, + "categories": [ + { + "id": "eletricreheating", + "name": "electric reheating", + "charts": [ + { + "title": "Dual Mode Reheating Temperature", + "id": "reheatingtemp", + "unit": "Celsius", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Heating", + "id": "dualmodeheatingtemp", + "regex": "DUAL MODE TEMP HEATING<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + }, + { + "name": "Hot Water", + "id" : "dualmodehotwatertemp", + "regex": "DUAL MODE TEMP DHW<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + } + ] + }, + { + "id": "roomtemp", + "name": "room temperature", + "charts": [ + { + "title": "Heat Circuit 1", + "id": "hc1", + "unit": "Celsius", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "<tr class=\"even\">\\s*<td.*>ACTUAL TEMPERATURE HC 1<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + }, + { + "name": "Set", + "id" : "set", + "regex": "<tr class=\"odd\">\\s*<td.*>SET TEMPERATURE HC 1<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + } + ] + }, + { + "title": "Heat Circuit 2", + "id": "hc2", + "unit": "Celsius", + "type": "line", + "prio": 2, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "<tr class=\"even\">\\s*<td.*>ACTUAL TEMPERATURE HC 2<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + }, + { + "name": "Set", + "id" : "set", + "regex": "<tr class=\"odd\">\\s*<td.*>SET TEMPERATURE HC 2<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + } + ] + } + ] + }, + { + "id": "heating", + "name": "heating", + "charts": [ + { + "title": "Heat Circuit 1", + "id": "hc1", + "unit": "Celsius", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "<tr class=\"odd\">\\s*<td.*>ACTUAL TEMPERATURE HC 1<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + }, + { + "name": "Set", + "id" : "set", + "regex": "<tr class=\"even\">\\s*<td.*>SET TEMPERATURE HC 1<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + } + ] + }, + { + "title": "Heat Circuit 2", + "id": "hc2", + "unit": "Celsius", + "type": "line", + "prio": 2, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "<tr class=\"odd\">\\s*<td.*>ACTUAL TEMPERATURE HC 2<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + }, + { + "name": "Set", + "id" : "set", + "regex": "<tr class=\"even\">\\s*<td.*>SET TEMPERATURE HC 2<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + } + ] + }, + { + "title": "Flow Temperature", + "id": "flowtemp", + "unit": "Celsius", + "type": "line", + "prio": 3, + "dimensions": [ + { + "name": "Heating", + "id": "heating", + "regex": "ACTUAL FLOW TEMPERATURE WP<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + }, + { + "name": "Reheating", + "id" : "reheating", + "regex": "ACTUAL FLOW TEMPERATURE NHZ<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + }, + { + "title": "Buffer Temperature", + "id": "buffertemp", + "unit": "Celsius", + "type": "line", + "prio": 4, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "ACTUAL BUFFER TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + }, + { + "name": "Set", + "id" : "set", + "regex": "SET BUFFER TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + }, + { + "title": "Fixed Temperature", + "id": "fixedtemp", + "unit": "Celsius", + "type": "line", + "prio": 5, + "dimensions": [ + { + "name": "Set", + "id" : "setfixed", + "regex": "SET FIXED TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + }, + { + "title": "Pre-flow Temperature", + "id": "preflowtemp", + "unit": "Celsius", + "type": "line", + "prio": 6, + "dimensions": [ + { + "name": "Actual", + "id": "actualreturn", + "regex": "ACTUAL RETURN TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + } + ] + }, + { + "id": "hotwater", + "name": "hot water", + "charts": [ + { + "title": "Hot Water Temperature", + "id": "hotwatertemp", + "unit": "Celsius", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Actual", + "id": "actual", + "regex": "ACTUAL TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + }, + { + "name": "Set", + "id" : "set", + "regex": "SET TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + } + ] + }, + { + "id": "general", + "name": "general", + "charts": [ + { + "title": "Outside Temperature", + "id": "outside", + "unit": "Celsius", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Outside temperature", + "id": "outsidetemp", + "regex": "OUTSIDE TEMPERATURE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>\\s*<\\\/tr>" + } + ] + }, + { + "title": "Condenser Temperature", + "id": "condenser", + "unit": "Celsius", + "type": "line", + "prio": 2, + "dimensions": [ + { + "name": "Condenser", + "id": "condenser", + "regex": "CONDENSER TEMP\\.<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + }, + { + "title": "Heating Circuit Pressure", + "id": "heatingcircuit", + "unit": "bar", + "type": "line", + "prio": 3, + "dimensions": [ + { + "name": "Heating Circuit", + "id": "heatingcircuit", + "digits": 2, + "regex": "PRESSURE HTG CIRC<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]*).*<\\\/td>" + } + ] + }, + { + "title": "Flow Rate", + "id": "flowrate", + "unit": "liters/min", + "type": "line", + "prio": 4, + "dimensions": [ + { + "name": "Flow Rate", + "id": "flowrate", + "digits": 2, + "regex": "FLOW RATE<\\\/td>\\s*<td.*>(-?[0-9]+,[0-9]+).*<\\\/td>" + } + ] + }, + { + "title": "Output", + "id": "output", + "unit": "%", + "type": "line", + "prio": 5, + "dimensions": [ + { + "name": "Heat Pump", + "id": "outputheatpump", + "regex": "OUTPUT HP<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*).*<\\\/td>" + }, + { + "name": "Water Pump", + "id": "intpumprate", + "regex": "INT PUMP RATE<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*).*<\\\/td>" + } + ] + } + ] + } + ] + }, + { + "name": "Heat Pump", + "id": "heatpump", + "url": "http://machine.ip.or.dns/?s=1,1", + "update_every": 10, + "categories": [ + { + "id": "runtime", + "name": "runtime", + "charts": [ + { + "title": "Compressor", + "id": "compressor", + "unit": "h", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Heating", + "id": "heating", + "regex": "RNT COMP 1 HEA<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + }, + { + "name": "Hot Water", + "id" : "hotwater", + "regex": "RNT COMP 1 DHW<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + } + ] + }, + { + "title": "Reheating", + "id": "reheating", + "unit": "h", + "type": "line", + "prio": 2, + "dimensions": [ + { + "name": "Reheating 1", + "id": "rh1", + "regex": "BH 1<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + }, + { + "name": "Reheating 2", + "id" : "rh2", + "regex": "BH 2<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + } + ] + } + ] + }, + { + "id": "processdata", + "name": "process data", + "charts": [ + { + "title": "Remaining Compressor Rest Time", + "id": "remaincomp", + "unit": "s", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Timer", + "id": "timer", + "regex": "COMP DLAY CNTR<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + } + ] + } + ] + }, + { + "id": "energy", + "name": "energy", + "charts": [ + { + "title": "Compressor Today", + "id": "compressorday", + "unit": "kWh", + "type": "line", + "prio": 1, + "dimensions": [ + { + "name": "Heating", + "id": "heating", + "digits": 3, + "regex": "COMPRESSOR HEATING DAY<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + }, + { + "name": "Hot Water", + "id": "hotwater", + "digits": 3, + "regex": "COMPRESSOR DHW DAY<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + } + ] + }, + { + "title": "Compressor Total", + "id": "compressortotal", + "unit": "MWh", + "type": "line", + "prio": 2, + "dimensions": [ + { + "name": "Heating", + "id": "heating", + "digits": 3, + "regex": "COMPRESSOR HEATING TOTAL<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + }, + { + "name": "Hot Water", + "id": "hotwater", + "digits": 3, + "regex": "COMPRESSOR DHW TOTAL<\\\/td>\\s*<td.*>(-?[0-9]+,?[0-9]*)" + } + ] + } + ] + } + ] + } + ] +} +``` diff --git a/collectors/node.d.plugin/stiebeleltron/stiebeleltron.node.js b/collectors/node.d.plugin/stiebeleltron/stiebeleltron.node.js new file mode 100644 index 000000000..250c26540 --- /dev/null +++ b/collectors/node.d.plugin/stiebeleltron/stiebeleltron.node.js @@ -0,0 +1,197 @@ +'use strict'; +// SPDX-License-Identifier: GPL-3.0-or-later + +// This program will connect to one Stiebel Eltron ISG for heatpump heating +// to get the heat pump metrics. + +// example configuration in netdata/conf.d/node.d/stiebeleltron.conf.md + +require("url"); +require("http"); +var netdata = require("netdata"); + +netdata.debug("loaded " + __filename + " plugin"); + +var stiebeleltron = { + name: "Stiebel Eltron", + enable_autodetect: false, + update_every: 10, + base_priority: 60000, + charts: {}, + pages: {}, + + createBasicDimension: function (id, name, multiplier, divisor) { + return { + id: id, // the unique id of the dimension + name: name, // the name of the dimension + algorithm: netdata.chartAlgorithms.absolute,// the id of the netdata algorithm + multiplier: multiplier, // the multiplier + divisor: divisor, // the divisor + hidden: false // is hidden (boolean) + }; + }, + + processResponse: function (service, html) { + if (html === null) return; + + // add the service + service.commit(); + + var page = stiebeleltron.pages[service.name]; + var categories = page.categories; + var categoriesCount = categories.length; + while (categoriesCount--) { + var context = { + html: html, + service: service, + category: categories[categoriesCount], + page: page, + chartDefinition: null, + dimension: null + }; + stiebeleltron.processCategory(context); + + } + }, + + processCategory: function (context) { + var charts = context.category.charts; + var chartCount = charts.length; + while (chartCount--) { + context.chartDefinition = charts[chartCount]; + stiebeleltron.processChart(context); + } + }, + + processChart: function (context) { + var dimensions = context.chartDefinition.dimensions; + var dimensionCount = dimensions.length; + context.service.begin(stiebeleltron.getChartFromContext(context)); + + while (dimensionCount--) { + context.dimension = dimensions[dimensionCount]; + stiebeleltron.processDimension(context); + } + context.service.end(); + }, + + processDimension: function (context) { + var dimension = context.dimension; + var match = new RegExp(dimension.regex).exec(context.html); + if (match === null) return; + var value = match[1].replace(",", "."); + // most values have a single digit by default, which requires the values to be multiplied. can be overridden. + if (stiebeleltron.isDefined(dimension.digits)) { + value *= Math.pow(10, dimension.digits); + } else { + value *= 10; + } + context.service.set(stiebeleltron.getDimensionId(context), value); + }, + + getChartFromContext: function (context) { + var chartId = this.getChartId(context); + var chart = stiebeleltron.charts[chartId]; + if (stiebeleltron.isDefined(chart)) return chart; + + var chartDefinition = context.chartDefinition; + var service = context.service; + var dimensions = {}; + + var dimCount = chartDefinition.dimensions.length; + while (dimCount--) { + var dim = chartDefinition.dimensions[dimCount]; + var multiplier = 1; + var divisor = 10; + if (stiebeleltron.isDefined(dim.digits)) divisor = Math.pow(10, Math.max(0, dim.digits)); + if (stiebeleltron.isDefined(dim.multiplier)) multiplier = dim.multiplier; + if (stiebeleltron.isDefined(dim.divisor)) divisor = dim.divisor; + context.dimension = dim; + var dimId = this.getDimensionId(context); + dimensions[dimId] = this.createBasicDimension(dimId, dim.name, multiplier, divisor); + } + + chart = { + id: chartId, + name: '', + title: chartDefinition.title, + units: chartDefinition.unit, + family: context.category.name, + context: 'stiebeleltron.' + context.category.id + '.' + chartDefinition.id, + type: chartDefinition.type, + priority: stiebeleltron.base_priority + chartDefinition.prio,// the priority relative to others in the same family + update_every: service.update_every, // the expected update frequency of the chart + dimensions: dimensions + }; + chart = service.chart(chartId, chart); + stiebeleltron.charts[chartId] = chart; + + return chart; + }, + + // module.serviceExecute() + // this function is called only from this module + // its purpose is to prepare the request and call + // netdata.serviceExecute() + serviceExecute: function (name, uri, update_every) { + netdata.debug(this.name + ': ' + name + ': url: ' + uri + ', update_every: ' + update_every); + + var service = netdata.service({ + name: name, + request: netdata.requestFromURL(uri), + update_every: update_every, + module: this + }); + service.execute(this.processResponse); + }, + + + configure: function (config) { + if (stiebeleltron.isUndefined(config.pages)) return 0; + var added = 0; + var pageCount = config.pages.length; + while (pageCount--) { + var page = config.pages[pageCount]; + // some validation + if (stiebeleltron.isUndefined(page.categories) || page.categories.length < 1) { + netdata.error("Your Stiebel Eltron config is invalid. Disabling plugin."); + return 0; + } + if (stiebeleltron.isUndefined(page.update_every)) page.update_every = this.update_every; + this.pages[page.name] = page; + this.serviceExecute(page.name, page.url, page.update_every); + added++; + } + return added; + }, + + // module.update() + // this is called repeatedly to collect data, by calling + // netdata.serviceExecute() + update: function (service, callback) { + service.execute(function (serv, data) { + service.module.processResponse(serv, data); + callback(); + }); + }, + + getChartId: function (context) { + return "stiebeleltron_" + context.page.id + + "." + context.category.id + + "." + context.chartDefinition.id; + }, + + getDimensionId: function (context) { + return context.dimension.id; + }, + + isUndefined: function (value) { + return typeof value === 'undefined'; + }, + + isDefined: function (value) { + return typeof value !== 'undefined'; + } +}; + +module.exports = stiebeleltron; |