iobroker.sainlogic
Version:
Read data from a sainlogic based weather station
227 lines (198 loc) • 9.71 kB
JavaScript
/* 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;