/** * Javascript module for helper methods with InfluxDB. * * Copyright (c) 2022 Markus Sipilä. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /** * Exports. */ module.exports = { getCurrentControl: getCurrentControl, getPrices: getPrices, getPoints: getPoints, writePoints: writePoints, deletePoints: deletePoints, parseCSV: parseCSV } /** * Returns InfluxDB connection parameters. * * @return Object * Object with connection properties. */ function getConnectionParams() { const params = { baseUrl: 'http://localhost:8086/api/v2/', org: 'openhab', bucket: 'autogen', token: 'insert-your-influx-token-here' } return params; } /** * Returns current control value for the given measurement and moment. * * @param string measurement * Name of the Influx measurement. * @param Date start * Pass current full hour as a Date object. * * @return int * Returns 1 or 0. If no control is found, defaults to 1. */ function getCurrentControl(measurement, start) { console.log('influx.js: Getting the current control value for ' + measurement + '...'); const stop = new Date(start.getTime()); stop.setHours(stop.getHours() + 1); const csv = getPoints(measurement, start, stop); const points = parseCSV(csv, 5, 6); if (points.length) { const control = points[0].value; console.log('influx.js: Current control: ' + control); return control; } else { console.log('influx.js: Current control not found, defaulting to 1!'); return 1; } } /** * Reads spot prices from the database. * * @param Date start * Start of the time range. * @param Date stop * Stop of the time range. * * @return array * Array of spot price objects. */ function getPrices(start, stop) { console.log('influx.js: Reading spot prices from the database...'); const csv = getPoints('spot_price', start, stop); const prices = parseCSV(csv, 5, 6); return prices; } /** * Reads points of given measurements from given range. * * @param string measurement * Name of the Influx measurement. * @param Date start * Start of the range as a Date object. * @param Date stop * Stop of the range as a Date object. * * @return string * Influx response in CSV format. */ function getPoints(measurement, start, stop) { const http = Java.type("org.openhab.core.model.script.actions.HTTP"); const params = getConnectionParams(); const url = params.baseUrl + 'query?' + 'org=' + params.org + '&bucket=' + params.bucket; const headers = { 'Authorization': 'Token ' + params.token, 'Content-Type': 'application/vnd.flux' }; const fluxQuery = 'from(bucket: \"' + params.bucket + '\") ' + '|> range(start: ' + start.toISOString() + ', stop: ' + stop.toISOString() + ') ' + '|> filter(fn: (r) => r[\"_measurement\"] == \"' + measurement + '\")'; // console.debug('influx.js: ' + fluxQuery); let response = ''; try { response = http.sendHttpPostRequest(url, 'application/json', fluxQuery, headers, 5000); // console.debug('influx.js: Points read from the database.'); } catch (exception) { console.error('influx.js: Exception reading points from the database: ' + exception.message); } return response; } /** * Parses the timestamps and values from the CSV response. * * @param string csv * CSV response from Influx. * @param int colTimestamp * Column index for the timestamp. * @param colValue * Column index for the value. * * @return array * Array of point objects. */ function parseCSV(csv, colDatetime, colValue) { let results = []; const rows = csv.split('\n'); const n = rows.length; // Ignore the header row and last empty rows. for (let i = 1; i < n-2; i++) { // console.debug('influx.js: ' + rows[i]); let row = rows[i]; let cols = row.split(','); let point = { datetime: cols[colDatetime], value: parseFloat(cols[colValue]) } results.push(point); } if (results.length == 0) { console.warn('influx.js: query did not return any data!'); } return results; } /** * Writes points to the database. * * @param string measurement * Name of the Influx measurement. * @param array points * Array of timestamp-value objects. * * @return bool */ function writePoints(measurement, points) { const n = Object.keys(points).length; console.log('influx.js: Preparing to write ' + n + ' points to the database for ' + measurement); const http = Java.type("org.openhab.core.model.script.actions.HTTP"); const params = getConnectionParams(); const url = params.baseUrl + 'write?' + 'org=' + params.org + '&bucket=' + params.bucket + '&precision=ms'; const headers = { Authorization: 'Token ' + params.token }; let response = false; try { for (let i = 0; i < n; i++) { let value = points[i].value; let datetime = points[i].datetime; let timestamp = new Date(datetime).getTime(); let data = measurement + ' value=' + value + ' ' + timestamp; //console.debug('influx.js: ' + data); http.sendHttpPostRequest(url, 'application/json', data, headers, 5000); } response = true; console.log('influx.js: Points successfully saved for measurement ' + measurement); } catch (exception) { console.error('influx.js: Exception saving points for measurement ' + measurement + ': ' + exception.message); } return response; } /** * Deletes previously saved points for given measurement and given range. * * @param string measurement * Name of the Influx measurement. * @param string start * Start datetime of the range in ISO8601 format, e.g. 2022-06-27T00:00:00.000Z * @param string stop * Stop datetime of the range in ISO8601 format, e.g. 2022-06-28T00:00:00.000Z */ function deletePoints(measurement, start, stop) { const http = Java.type("org.openhab.core.model.script.actions.HTTP"); const params = getConnectionParams(); const url = params.baseUrl + 'delete?' + 'org=' + params.org + '&bucket=' + params.bucket; const headers = { Authorization: 'Token ' + params.token }; const deleteFlux = { start: start, stop: stop, predicate: '_measurement="' + measurement + '"' }; try { http.sendHttpPostRequest(url, 'application/json', JSON.stringify(deleteFlux), headers, 5000); console.log('influx.js: Points successfully deleted. Measurement: ' + measurement + '. Range: ' + start + ' - ' + stop); } catch (exception) { console.error('influx.js: Exception deleting points. Measurement: ' + measurement + '. Range: ' + start + ' - ' + stop + '. Exception: ' + exception.message); } }