iobroker.sainlogic
Version:
Read data from a sainlogic based weather station
213 lines (167 loc) • 8.77 kB
JavaScript
/* jslint node: true */
'use strict';
const url = require('url');
const http = require('http');
const got = require('@esm2cjs/got').default;
const Parser = require('expr-eval').Parser;
const { parse } = require('querystring');
const { PROT_WU, PROT_EW, DATAFIELDS } = require('./constants');
/**
* Listener class
*
* @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
* @return {object} object instance
*/
class Listener {
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);
}
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');
}
}
stop() {
if (this.webServer)
this.webServer.close(() => {
this.adapter.log.info('Listener closed.');
});
}
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}`);
}
}
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;