UNPKG

iobroker.sainlogic

Version:

Read data from a sainlogic based weather station

227 lines (198 loc) 9.71 kB
/* jslint node: true */ 'use strict'; const url = require('node:url'); const http = require('node:http'); const got = require('@esm2cjs/got').default; const Parser = require('expr-eval').Parser; const { parse } = require('node:querystring'); const { PROT_WU, PROT_EW, DATAFIELDS } = require('./constants'); /** * Listener class * * @param {string} address IP Address to listen on * @param {number} port Port to listen on * @param {string} path URL / Path whrer updates come in * @param {object} adapter web adapter object * @returns {object} object instance */ class Listener { /** * @param {string} address IP Address to listen on * @param {number} port Port to listen on * @param {string} path URL / Path whrer updates come in * @param {string} protocol Protocol used (WU or EW) * @param {string} fwd_url Forward URL (if any) * @param {object} adapter web adapter object */ constructor(address, port, path, protocol, fwd_url, adapter) { this.address = address; this.port = port; this.path = path; this.protocol = protocol; this.forward_url = fwd_url; this.adapter = adapter; this.webServer = null; this.adapter.log.debug(`Listener IP: ${this.address}`); this.adapter.log.debug(`Listener port: ${this.port}`); this.adapter.log.debug(`Listener path: ${this.path}`); this.adapter.log.debug(`Listener protocol: ${this.protocol}`); this.adapter.log.debug(`Forward URL: ${this.forward_url}`); } /** * starts the listener */ start() { this.adapter.log.info('Starting Listener'); try { /** * @param {{ url: string; }} request * @param {{ writeHead: (arg0: number, arg1: { "Content-Type": string; }) => void; end: () => void; }} response */ this.webServer = http.createServer((request, response) => { let my_body; let json_response; const my_url = url.parse(request.url, true); const my_path = my_url.pathname; // Wunderground switch (this.protocol) { case PROT_WU: json_response = my_url.query; if (my_path == this.path) { this.adapter.log.debug(`Listener received WU update: ${JSON.stringify(json_response)}`); // add full string property json_response['last_listener_update'] = JSON.stringify(json_response); // this.adapter.setStateAsync('info.last_listener_update', { val: JSON.stringify(json_response), ack: true }); response.writeHead(200, { 'Content-Type': 'text/html' }); response.end(); this.adapter.setStates(new Date(), this.extract_values(json_response)); if (this.forward_url != null && this.forward_url != '') { // forward to another host const fwd_url = new URL(this.forward_url); got(fwd_url, { searchParams: json_response, method: 'GET', retry: { limit: 0 } }) .then(got_response => { this.adapter.log.debug(`Forward URL: + ${got_response.requestUrl}`); this.adapter.log.debug(`Forward response body: ${got_response.body}`); }) .catch(error => { this.adapter.log.info(error); }); } } else { this.adapter.log.warn(`Listener received illegal request: ${request.url}`); response.writeHead(400, { 'Content-Type': 'text/html' }); response.end(); } break; case PROT_EW: if (request.method == 'POST' && my_path == this.path) { my_body = ''; request.on('data', chunk => { my_body += chunk.toString(); }); request.on('end', () => { this.adapter.log.debug(`Listener body is ${my_body}`); json_response = parse(my_body); this.adapter.log.debug(`Listener received EW update: ${JSON.stringify(json_response)}`); json_response['last_listener_update'] = JSON.stringify(json_response); //this.adapter.setStateAsync('info.last_listener_update', { val: JSON.stringify(json_response), ack: true }); response.end('ok'); this.adapter.setStates(new Date(), this.extract_values(json_response)); // forwarding needs to happen when request is complete, otherwise we send empty payload if (this.forward_url != null && this.forward_url != '') { // forward to another host const fwd_url = new URL(this.forward_url); got(fwd_url, { method: 'POST', retry: { limit: 0 }, body: my_body }) .then(got_response => { this.adapter.log.debug(`Forward URL: + ${got_response.requestUrl}`); this.adapter.log.debug(`Forward response body: ${got_response.body}`); }) .catch(error => { this.adapter.log.info(error); }); } }); } else { this.adapter.log.warn( `Listener received illegal request: (${request.method}) ${request.url}`, ); response.writeHead(400, { 'Content-Type': 'text/html' }); response.end(); } break; } }); this.webServer.on('error', this.server_error.bind(this)); this.webServer.listen(this.port, this.address); } catch (e) { this.adapter.log.error(`Something else went wrong on starting our Listener: ${e}`); } } /** * stops the listener */ stop() { if (this.webServer) { this.webServer.close(() => { this.adapter.log.info('Listener closed.'); }); } } /** * error handler for server * * @param e Error object */ server_error(e) { if (e.toString().includes('EACCES') && this.port <= 1024) { this.adapter.log.error( `node.js process has no rights to start server on the port ${this.port}.\n` + `Do you know that on linux you need special permissions for ports under 1024?\n` + `You can call in shell following scrip to allow it for node.js: "iobroker fix"`, ); } else { this.adapter.log.error(`Cannot start server on ${this.address || '0.0.0.0'}:${this.port}: ${e}`); } } /** * Extracts the values from the received JSON response * * @param json_response JSON Object received from the listener * @returns array of extracted values */ extract_values(json_response) { const myobj = {}; const parser = new Parser(); const protocol = this.protocol; for (const dataelement in json_response) { let element_index = ''; const my_attr_def = DATAFIELDS.filter(function (def) { const retval = dataelement.match(def[protocol]); if (retval && retval.length == 2) { element_index = retval[1]; } return retval; }); // expecting only one target data point to store value if (my_attr_def.length > 0) { const target_data_point = my_attr_def[0]; // Select first channel entry of datapoint of channel for new datapoint let channel = `${target_data_point.channels[0].channel}.`; // add element index to channel if not empty if (element_index && element_index != '') { channel = `${channel + element_index}.`; } const c_id = channel + target_data_point.id; this.adapter.log.debug(`Extracting value for ${c_id}(${dataelement})`); myobj[c_id] = json_response[dataelement]; if (target_data_point.listener_conversion != null) { const exp = parser.parse(target_data_point.listener_conversion); myobj[c_id] = exp.evaluate({ x: myobj[c_id] }); } } else { this.adapter.log.debug(`No mapping found for dataelement: ${dataelement}`); } } return myobj; } } module.exports = Listener;