iobroker.bidirectional-counter
Version:
Counter to separate consumption (positive changes) and deliverd (negative changes)
329 lines (302 loc) • 13.6 kB
JavaScript
'use strict';
/*
* Created with @iobroker/create-adapter v2.1.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require('@iobroker/adapter-core');
// Load your modules here, e.g.:
// const fs = require("fs");
class BidirectionalCounter extends utils.Adapter {
/**
* @param [options] options of the adapter
*/
constructor(options) {
super({
...options,
name: 'bidirectional-counter',
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
this.on('objectChange', this.onObjectChange.bind(this));
// this.on("message", this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
this.subscribecounterId = 'info.subscribedStatesCount';
this.subscribecounter = 0;
this.additionalIds = {
consumed: '.consumed',
delivered: '.delivered',
total: '.total',
};
// define arrays for selected states and calculation
this.activeStates = {};
this.activeStatesLastAdditionalValues = {};
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Initialize your adapter here
//Read all states with custom configuration
const customStateArray = await this.getObjectViewAsync('system', 'custom', {});
// Request if there is an object
if (customStateArray && customStateArray.rows) {
for (let index = 0; index < customStateArray.rows.length; index++) {
if (customStateArray.rows[index].value !== null) {
// Request if there is an object for this namespace an its enabled
if (
customStateArray.rows[index].value[this.namespace] &&
customStateArray.rows[index].value[this.namespace].enabled === true
) {
const id = customStateArray.rows[index].id;
const obj = await this.getForeignObjectAsync(id);
if (obj) {
const common = obj.common;
const state = await this.getForeignStateAsync(id);
if (state) {
await this.addObjectAndCreateState(
id,
common,
customStateArray.rows[index].value[this.namespace],
state,
true,
);
}
}
}
}
}
}
this.subscribeForeignObjects('*');
this.setState(this.subscribecounterId, this.subscribecounter, true);
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback function wich is called after shutdown adapter
*/
onUnload(callback) {
try {
// Here you must clear all timeouts or intervals that may still be active
// clearTimeout(timeout1);
// clearTimeout(timeout2);
// ...
// clearInterval(interval1);
callback();
} catch (e) {
this.log.error(e);
callback();
}
}
// Creats a state with the given id
async addObjectAndCreateState(id, common, customInfo, state, countUpSubscibecounter) {
// check if custominfo is available
if (!customInfo) {
return;
}
if (common.type != 'number') {
this.log.error(`state ${id} is not type number, but ${common.type}`);
return;
}
this.activeStates[id] = {
lastValue: state.val,
enableFallbackToZero: customInfo.enableFallbackToZero,
};
// Create adapter internal object
const tempId = this.createStatestring(id);
await this.setObjectAsync(tempId, {
type: 'channel',
common: {
name: customInfo.channelName,
},
native: {},
});
// create adapter internal states
for (const myId in this.additionalIds) {
const tempId = this.createStatestring(id) + this.additionalIds[myId];
await this.setObjectNotExistsAsync(tempId, {
type: 'state',
common: {
name: common.name,
type: 'number',
role: common.role,
unit: common.unit,
read: true,
write: true,
def: 0,
},
native: {},
});
this.log.debug(`state ${tempId} added / activated`);
this.subscribeStates(tempId);
const lastState = await this.getStateAsync(tempId);
if (lastState !== undefined && lastState !== null) {
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = lastState.val;
} else {
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = 0;
}
this.setState(tempId, this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`], true);
}
// Subcribe main state
if (countUpSubscibecounter) {
this.subscribeForeignStates(id);
this.subscribecounter += 1;
this.setState(this.subscribecounterId, this.subscribecounter, true);
}
}
createStatestring(id) {
return `counted_Values.${id.replace(/\./g, '_')}`;
}
// clear the state from the active array. if selected the state will be deleted
async clearStateArrayElement(id, deleteState) {
// Unsubscribe and delete states if exists
if (this.activeStates[id]) {
delete this.activeStates[id];
this.subscribecounter -= 1;
this.setState(this.subscribecounterId, this.subscribecounter, true);
if (!this.activeStatesLastAdditionalValues[id]) {
// Dont unsubscribe in case of is additional value
this.unsubscribeForeignStates(id);
this.log.debug(`state ${id} not longer subscribed`);
} else {
this.log.debug(`state ${id} not longer subscribed as active state, but still as additional`);
}
}
if (this.config.deleteStatesWithDisable || deleteState) {
for (const myId in this.additionalIds) {
const tempId = this.createStatestring(id) + this.additionalIds[myId];
const myObj = await this.getObjectAsync(tempId);
if (myObj) {
this.unsubscribeStates(tempId);
this.log.debug(`state ${tempId} removed`);
this.delObjectAsync(tempId);
this.log.debug(`state ${this.namespace}.${tempId} deleted`);
}
}
// Delete channel Object
this.delObjectAsync(this.createStatestring(id));
}
}
/***************************************************************************************
* ********************************** Changes ******************************************
***************************************************************************************/
async onObjectChange(id, obj) {
if (obj) {
try {
if (!obj.common.custom || !obj.common.custom[this.namespace]) {
if (this.activeStates[id]) {
this.clearStateArrayElement(id, false);
return;
}
} else {
const customInfo = obj.common.custom[this.namespace];
if (this.activeStates[id]) {
const state = await this.getForeignStateAsync(id);
if (state) {
await this.addObjectAndCreateState(id, obj.common, customInfo, state, false);
}
} else {
const state = await this.getForeignStateAsync(id);
if (state) {
this.addObjectAndCreateState(id, obj.common, customInfo, state, true);
} else {
this.log.error(`could not read state ${id}`);
}
}
}
} catch (error) {
this.log.error(error);
this.clearStateArrayElement(id, false);
}
} else {
// The object was deleted
// Check if the object is kwnow
const obj = await this.getObjectAsync(this.createStatestring(id) + this.additionalIds.consumed);
if (this.activeStates[id] || obj) {
this.clearStateArrayElement(id, true);
}
}
}
/**
* Is called if a subscribed state changes
*
* @param id id of the changed state
* @param state state (val & ack) of the changed state-id
*/
onStateChange(id, state) {
if (state) {
// Check if state.val is reachable
if (state.val !== undefined && state.val !== null) {
// Check Changes in Foreign states
if (this.activeStates[id]) {
if (state.val !== 0 || this.activeStates[id].enableFallbackToZero) {
const difference = Number(state.val) - this.activeStates[id].lastValue;
this.log.debug(
`${id} changed from ${this.activeStates[id].lastValue} to ${Number(state.val)} - Difference: ${difference}`,
);
if (difference >= 0) {
const tempId = this.createStatestring(id) + this.additionalIds.consumed;
const tempValue =
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] + difference;
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = tempValue;
this.setStateAsync(tempId, tempValue, true);
this.log.debug(`${tempId} is set to ${tempValue}`);
} else {
const tempId = this.createStatestring(id) + this.additionalIds.delivered;
const tempValue =
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] - difference;
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = tempValue;
this.setStateAsync(tempId, tempValue, true);
this.log.debug(`${tempId} is set to ${tempValue}`);
}
const tempId = this.createStatestring(id) + this.additionalIds.total;
const tempValue =
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] + difference;
this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = tempValue;
this.setStateAsync(tempId, tempValue, true);
this.log.debug(`${tempId} is set to ${tempValue}`);
}
this.activeStates[id].lastValue = state.val;
}
// Check Changes in internal States (also if id is active state)
if (
this.activeStatesLastAdditionalValues[id] !== undefined &&
this.activeStatesLastAdditionalValues[id] !== null &&
!state.ack
) {
this.activeStatesLastAdditionalValues[id] = state.val;
this.setStateAsync(id, state.val, true);
}
}
} else {
// The state was deleted
this.log.debug(`state ${id} deleted`);
}
}
// If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.
// /**
// * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
// * Using this method requires "common.messagebox" property to be set to true in io-package.json
// * @param {ioBroker.Message} obj
// */
// onMessage(obj) {
// if (typeof obj === "object" && obj.message) {
// if (obj.command === "send") {
// // e.g. send email or pushover or whatever
// this.log.debug("send command");
// // Send response in callback if required
// if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback);
// }
// }
// }
}
if (require.main !== module) {
// Export the constructor in compact mode
/**
* @param [options] options of the adapter
*/
module.exports = options => new BidirectionalCounter(options);
} else {
// otherwise start the instance directly
new BidirectionalCounter();
}