iobroker.sainlogic
Version:
Read data from a sainlogic based weather station
290 lines (236 loc) • 10.3 kB
JavaScript
/* jshint node: true */
// @ts-nocheck
'use strict';
/*
* Created with @iobroker/create-adapter v1.24.1
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require('@iobroker/adapter-core');
//const util = require('util');
const Parser = require('expr-eval').Parser;
// Load your modules here, e.g.:
const Listener = require('./lib/listener');
const Scheduler = require('./lib/scheduler');
const { PROT_WU, PROT_EW, DATAFIELDS } = require('./lib/constants');
//const getMethods = (obj) => Object.getOwnPropertyNames(obj).filter(item => typeof obj[item] === 'function');
class Sainlogic extends utils.Adapter {
/**
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
*/
// eslint-disable-next-line no-unused-vars
constructor(options) {
super({
name: 'sainlogic',
});
this.on('ready', this.onReady.bind(this));
// this.on('objectChange', this.onObjectChange.bind(this));
//this.on('stateChange', this.onStateChange.bind(this));
// this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
}
checkUnit(attrdef, obj) {
const c_id = obj._id;
const target_unit = this.config[attrdef.unit_config];
if (target_unit != obj.common.unit) {
// change and convert unit
this.log.info(`Unit changed for ${c_id} from ${obj.common.unit} to ${target_unit}, updating data point`);
this.extendObject(c_id, {
type: obj.type,
common: {
name: attrdef.display_name,
type: attrdef.type,
role: attrdef.role,
unit: target_unit,
},
native: {},
});
const my_source_unit = attrdef.units.filter(function (unit) {
return unit.display_name == obj.common.unit;
});
const conversion_rule_back = my_source_unit[0].main_unit_conversion;
const that = this;
this.getState(c_id, function (err, st) {
const parser = new Parser();
let new_value = st.val;
// convert back if required
if (conversion_rule_back != null) {
const exp = parser.parse(conversion_rule_back);
new_value = exp.evaluate({ x: new_value });
}
new_value = that.toDisplayUnit(attrdef, new_value);
that.setState(c_id, { val: new_value, ack: true });
}.bind(that));
}
}
/** Converts the given value to the target unit for given attribute definition */
toDisplayUnit(attrdef, value) {
const parser = new Parser();
const target_unit = this.config[attrdef.unit_config];
const my_target_unit = attrdef.units.filter(function (unit) {
return unit.display_name == target_unit;
});
const conversion_rule_forward = my_target_unit[0].display_conversion;
this.log.debug('Target for ' + attrdef.id + ' unit is set: ' + target_unit + ', using conversion: ' + conversion_rule_forward);
let new_value = value;
// convert forward if required
if (conversion_rule_forward != null) {
const exp = parser.parse(conversion_rule_forward);
new_value = exp.evaluate({ x: value });
}
return new_value;
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Initialize your adapter here
// The adapters config (in the instance object everything under the attribute "native") is accessible via
// this.config:
if (this.config.scheduler_active == true) {
this.log.info('Starting Scheduler');
this.scheduler = new Scheduler(this.config, this);
this.scheduler.start();
}
if (this.config.listener_active == true) {
this.listener = new Listener(this.config.bind, this.config.port, this.config.path, this.config.listener_protocol, this.config.listener_forward_url, this);
this.listener.start();
}
}
verify_datapoint(obj_id, that, attrdef, attrname, value) {
// check target type and type-cast if needed
let default_value = '';
if (attrdef.type == 'number'){
if (value != null) {
value = parseFloat(value);
} else {
value = 0;
default_value = 0;
}
}
this.getObject(obj_id, function (err, obj) {
if (err || obj == null) {
that.log.info('Creating new data point: ' + obj_id);
that.setObjectNotExists(obj_id, {
type: 'state',
common: {
name: attrname,
type: attrdef.type,
unit: attrdef.unit,
role: attrdef.role,
min: attrdef.min,
max: attrdef.max,
def: default_value,
read: true,
write: false,
mobile: {
admin: {
visible: true
}
},
},
native: {},
// eslint-disable-next-line no-unused-vars
}, function (err, obj) {
// now update the value
that.setStateAsync(obj_id, { val: value, ack: true });
});
}
else {
if (attrdef.unit_config != null) {
that.checkUnit(attrdef, obj);
}
// now update the value
that.setStateAsync(obj_id, { val: value, ack: true });
}
}.bind(that));
}
/**
* @param {Date} date
* @param {{ softwaretype: any; indoortempf: any; tempf: any; dewptf: any; windchillf: any; indoorhumidity: any; humidity: any; windspeedmph: any; windgustmph: any; winddir: any; baromin: any; absbaromin: any; ... 6 more ...; UV: any; }} json_response
*/
setStates(date, obj_values) {
this.setStateAsync('info.last_update', { val: date.toString(), ack: true });
for (const attr in obj_values) {
// extract attribute id w/o channel
const c_id = attr.split('.').pop();
// check if this has a mapping to the current protocol
this.log.debug(`Setting value from data for ${attr} to ${obj_values[attr]}`);
const my_attr_def = DATAFIELDS.filter(function (def) {
return def.id == c_id;
});
let display_val = obj_values[attr];
if (my_attr_def[0].unit_config) {
display_val = this.toDisplayUnit(my_attr_def[0], display_val);
}
this.verify_datapoint(attr, this, my_attr_def[0], my_attr_def[0].channels[0].name, display_val ); // allways channel 0 as primary attribute name
if (c_id == 'winddir') {
const winddir_attrdef = DATAFIELDS.filter(function (def) {
return def.id == 'windheading';
});
this.verify_datapoint('weather.current.windheading', this, winddir_attrdef[0], winddir_attrdef[0].channels[0].name, this.getHeading(display_val, 16) );
}
}
}
// taken from https://www.programmieraufgaben.ch/aufgabe/windrichtung-bestimmen/ibbn2e7d
getHeading(degrees, precision) {
this.log.debug('Determining wind heading for ' + degrees + ' and precision ' + precision);
precision = precision || 16;
let directions = [],
direction = 0;
const step = 360 / precision;
let i = step / 2;
switch (precision) {
case 4: directions = 'N O S W'.split(' '); break;
case 8: directions = 'N NO O SO S SW W NW'.split(' '); break;
case 16: directions = ('N NNO NO ONO O OSO SO ' +
'SSO S SSW SW WSW W WNW NW NNW').split(' ');
break;
case 32: directions = ('N NzO NNO NOzN NO NOzO ONO OzN O OzS OSO ' +
'SOzO SO SOzS SSO SzO S SzW SSW SWzS SW SWzW WSW WzS W WzN ' +
'WNW NWzW NW NWzN NNW NzW').split(' ');
break;
default:
this.log.error('Wind heading could not be determined: invalid precision');
return '';
}
if (degrees < 0 || degrees > 360) {
this.log.error('Wind heading could not be determined: wind direction outside boundary');
return '';
}
if (degrees <= i || degrees >= 360 - i) return 'N';
while (i <= degrees) {
direction++;
i += step;
}
this.log.debug('GetHeading returning: ' + directions[direction]);
return directions[direction];
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
*/
onUnload(callback) {
try {
if (this.listener)
this.listener.stop();
if (this.scheduler)
this.scheduler.stop();
this.log.info('Sainlogic Adapter gracefully shut down...');
callback();
} catch (e) {
callback();
}
}
}
// @ts-ignore parent is a valid property on module
if (module.parent) {
// Export the constructor in compact mode
/**
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
*/
module.exports = (options) => new Sainlogic(options);
} else {
// otherwise start the instance directly
new Sainlogic();
}