UNPKG

@hotzware/openhab-tools

Version:

Tools for the openHAB JavaScript Automation Add-On.

105 lines (97 loc) 4.42 kB
/** * Copyright (c) 2022 Florian Hotze * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ const { cache, items, log } = require('openhab'); const logger = log('org.openhab.automation.js.hotzware_openhab_tools.itemutils.dimmer'); /** * Dims an Item step-by-step to a target state. * * Only works for Items with support for float states. * The dimmer uses the shared cache to ensure that there are not multiple timers for the same Item active at the same time. * * @example * // Dim the Bedroom_Light to 50% in 750 seconds (1% each 15 seconds). * itemutils.dimItem('Bedroom_Light', 50.0, 1, 15 * 1000); * * @memberof itemutils * @param {string} itemName name of the Item to control * @param {number} targetState float number to dim to * @param {number} step dimming step size * @param {number} time time in milliseconds between each step * @param {boolean} [ignoreExternalChange=false] whether to break dimmer if Item receives a large external change * @param {boolean} [overwrite=false] whether to cancel an existing dimmer and create a new one * @throws {TypeError} when a parameter has wrong type * */ function dimItem (itemName, targetState, step, time, ignoreExternalChange = false, overwrite = false) { // Get Item and check parameters. const item = items.getItem(itemName); // @ts-ignore if (typeof targetState !== 'number') throw TypeError('targetState must be a number!'); if (typeof step !== 'number') throw TypeError('step must be a number!'); if (typeof time !== 'number') throw TypeError('time must be a number!'); const CACHE_KEY = 'hotzware_openhab-js-tools_dimmer-for-' + itemName; // If targetState already met, do not create a dimmer. if (items.getItem(itemName).numericState === targetState) { logger.debug(`${itemName} already has targetState ${targetState}, skipping.`); return; } // If targetState not met, create dimmer. // If dimmer for itemName already exists, do not create new one. const intervalIdFromCache = cache.shared.get(CACHE_KEY); if (intervalIdFromCache !== null) { if (overwrite) { clearInterval(intervalIdFromCache); cache.shared.put(CACHE_KEY, null); logger.info(`Dimmer for Item ${itemName} already exists, overwriting.`); } else { logger.info(`Dimmer for Item ${itemName} already exists, skipping.`); return; } } // Initialize and create dimmer. logger.info(`Dimming Item ${itemName} to ${targetState} ...`); let calculatedState = items.getItem(itemName).numericState; const intervalId = setInterval(() => { /** * Cancels the dimmer and removes the intervalId from the cache. * @param {string} msg message to log */ function breakDimmer (msg) { logger.info(`Dimmer for Item ${itemName} ${msg}`); clearInterval(intervalId); cache.shared.put(CACHE_KEY, null); } // Cancel when targetState is reached. // Workaround for an issue where the target state is never exactly met because the step size is too large and therefore the dimmer never ends. if (Math.abs(calculatedState - targetState) < step) { item.sendCommand(targetState.toString()); // Ensure the target state is met logger.trace(`Dimmer for ${itemName}: Sending command ${targetState}.`); breakDimmer('reached target state.'); return; } // Item receives large external change or state update on Item is really slow. const realState = items.getItem(itemName).numericState; if (Math.abs(realState - calculatedState) > 2) { logger.debug(`Dimmer ${itemName}: Difference between real state ${realState} and calculated state ${calculatedState} is large. External item change happened or state update is slow.`); if (!ignoreExternalChange) { breakDimmer('cancelled due to external change.'); return; } } // Dim to target state. calculatedState = calculatedState > targetState ? calculatedState - step : (calculatedState < targetState ? calculatedState + step : calculatedState); logger.trace(`Dimmer for ${itemName}: Sending command ${calculatedState}.`); item.sendCommand(calculatedState.toString()); }, time); cache.shared.put(CACHE_KEY, intervalId); } module.exports = { dimItem };