UNPKG

iobroker.drag-indicator

Version:
426 lines (399 loc) 18.1 kB
'use strict'; const objectStoreClass = require('./lib/modules/objectStore'); const DragindicatorDeviceManagement = require('./lib/modules/deviceManager/deviceManager'); /* * Created with @iobroker/create-adapter v2.1.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 schedule = require('node-schedule'); const { isDeepStrictEqual } = require('node:util'); // Load your modules here, e.g.: // const fs = require("node:fs"); class DragIndicator extends utils.Adapter { /** * @param [options] options of the adapter */ constructor(options) { super({ ...options, name: 'drag-indicator', }); 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 = { max: '.max', min: '.min', reset: '.reset', actual: '.actual', }; this.observedValuesId = 'observed_Values.'; this.cronJobs = {}; this.jobId = 'jobId'; // 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 this.subscribeForeignObjects('*'); this.subscribeStates('*'); // Generate Object Store this.objectStore = new objectStoreClass(this); await this.objectStore.generateStoreObjects(); // Device Manager this.deviceManagement = new DragindicatorDeviceManagement(this); //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.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 { // clear all schedules for (const cronJob in this.cronJobs) { schedule.cancelJob(this.cronJobs[cronJob][this.jobId]); } callback(); } catch (e) { this.log.error(e); callback(); } } 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] = {}; this.activeStates[id].customInfo = customInfo; // Create adapter internal object const tempId = this.createStatestring(id); await this.setObjectAsync(tempId, { type: 'channel', common: { name: customInfo.channelName, desc: id, }, native: {}, }); // create adapter internal states for (const myId in this.additionalIds) { const tempId = this.createStatestring(id) + this.additionalIds[myId]; if (this.additionalIds[myId] == this.additionalIds.reset) { await this.setObjectNotExistsAsync(tempId, { type: 'state', common: { name: myId, type: 'boolean', role: 'reset', read: true, write: true, def: false, }, native: {}, }); this.log.debug(`state ${tempId} added / activated`); this.subscribeStates(tempId); this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = id; this.setState(tempId, false, true); } else { await this.setObjectNotExistsAsync(tempId, { type: 'state', common: { name: common.name, type: common.type, role: common.role, unit: common.unit, read: true, write: this.additionalIds[myId] !== this.additionalIds.actual, }, native: {}, }); if (this.additionalIds[myId] !== this.additionalIds.actual) { 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}`] = state.val; this.setState(tempId, state.val, true); } } } if (customInfo.resetCronJob != '') { // check that ther is a cronjob configured if (!this.cronJobs[customInfo.resetCronJob]) { this.cronJobs[customInfo.resetCronJob] = {}; this.cronJobs[customInfo.resetCronJob][this.jobId] = schedule.scheduleJob( customInfo.resetCronJob, this.resetWithCronJob.bind(this, customInfo.resetCronJob), ); } this.cronJobs[customInfo.resetCronJob][this.createStatestring(id)] = {}; this.activeStates[id].lastCronJob = customInfo.resetCronJob; } else { this.activeStates[id].lastCronJob = ''; } // Subcribe main state if (countUpSubscibecounter) { this.subscribeForeignStates(id); this.subscribecounter += 1; this.setState(this.subscribecounterId, this.subscribecounter, true); } } // if the id is scheduled, it will be deleted from active array removefromCronJob(cronJob, id) { if (this.activeStates[id].lastCronJob != '') { delete this.cronJobs[cronJob][this.createStatestring(id)]; if (Object.keys(this.cronJobs[cronJob]).length <= 1) { this.log.debug(`job canceled: ${cronJob}`); schedule.cancelJob(this.cronJobs[cronJob][this.jobId]); delete this.cronJobs[cronJob]; } this.activeStates[id].lastCronJob = ''; } } createStatestring(id) { return `${this.observedValuesId}${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]) { this.removefromCronJob(this.activeStates[id].lastCronJob, 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.unsubscribeStatesAsync(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 { // Internal ObjectStore if (id.startsWith(this.objectStore?.startCondition)) { await this.objectStore?.updateDeviceObject(id, { payload: { object: obj } }); } 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) { if (!isDeepStrictEqual(this.activeStates[id].customInfo, customInfo)) { this.removefromCronJob(this.activeStates[id].lastCronJob, id); 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); } } } resetWithCronJob(cronJob) { for (const ele in this.cronJobs[cronJob]) { if (ele != this.jobId) { this.resetValues( `${this.namespace}.${ele}${this.additionalIds.reset}`, this.namespace.length, this.additionalIds.reset.length, ); } } } /** * Is called if a subscribed state changes * * @param id id of the changed state * @param state state (val & ack) of the changed state-id */ async onStateChange(id, state) { if (state) { // Internal ObjectStore if (id.startsWith(this.objectStore?.startCondition)) { await this.objectStore?.updateDeviceObject(id, { payload: { state: state } }); } // Check if state.val is reachable if (state.val !== undefined && state.val !== null) { // Check Changes in Foreign states if (this.activeStates[id]) { let tempId = this.createStatestring(id) + this.additionalIds.max; if (state.val > this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`]) { this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = state.val; this.setStateAsync(tempId, state.val, true); } else { tempId = this.createStatestring(id) + this.additionalIds.min; if (state.val < this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`]) { this.activeStatesLastAdditionalValues[`${this.namespace}.${tempId}`] = state.val; this.setStateAsync(tempId, state.val, true); } } // Set actual value to internal state tempId = this.createStatestring(id) + this.additionalIds.actual; this.setStateAsync(tempId, state.val, true); } // Check Changes in internal States (also if id is active state) if ( this.activeStatesLastAdditionalValues[id] !== undefined && this.activeStatesLastAdditionalValues[id] !== null && !state.ack ) { const extentionLength = this.additionalIds.reset.length; const extention = id.substring(id.length - extentionLength); const prefixLengt = this.namespace.length; const prefix = id.substring(0, prefixLengt); if (extention == this.additionalIds.reset && prefix == this.namespace) { // check that reset is true if (state.val == true) { this.resetValues(id, prefixLengt, extentionLength); this.setStateAsync(id, true, true); } else { this.setStateAsync(id, false, true); } } else { this.activeStatesLastAdditionalValues[id] = state.val; this.setStateAsync(id, state.val, true); } } } } else { // The state was deleted this.log.debug(`state ${id} deleted`); } } async resetValues(id, prefixLengt, extentionLength) { const subId = id.substring(prefixLengt + 1, id.length - extentionLength); // Get current state const curState = await this.getForeignStateAsync(this.activeStatesLastAdditionalValues[id]); if (curState) { this.activeStatesLastAdditionalValues[`${this.namespace}.${subId}${this.additionalIds.max}`] = curState.val; this.setStateAsync(subId + this.additionalIds.max, curState.val, true); this.activeStatesLastAdditionalValues[`${this.namespace}.${subId}${this.additionalIds.min}`] = curState.val; this.setStateAsync(subId + this.additionalIds.min, curState.val, true); } } // 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 (obj.command?.startsWith('dm:')) { // Handled by Device Manager class itself, so ignored here return; } 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 DragIndicator(options); } else { // otherwise start the instance directly new DragIndicator(); }