UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

714 lines • 29.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFromLookup = exports.toNumber = exports.assertNumber = exports.isBoolean = exports.isString = exports.isObject = exports.isNumber = exports.assertString = exports.assertArray = exports.assertObject = exports.printNumbersAsHexSequence = exports.printNumberAsHex = exports.attachOutputCluster = exports.noOccupancySince = exports.normalizeCelsiusVersionOfFahrenheit = exports.getClusterAttributeValue = exports.validateValue = exports.getObjectProperty = exports.getMetaValues = exports.getOptions = exports.getTransition = exports.getEntityOrFirstGroupMember = exports.getSceneState = exports.deleteSceneState = exports.saveSceneState = exports.getLabelFromName = exports.toCamelCase = exports.toSnakeCase = exports.sleep = exports.filterObject = exports.replaceInArray = exports.isInRange = exports.hasEndpoints = exports.getMetaValue = exports.batteryVoltageToPercentage = exports.getKey = exports.enforceEndpoint = exports.postfixWithEndpointName = exports.getEndpointName = exports.addActionGroup = exports.toPercentage = exports.calibrateAndPrecisionRoundOptions = exports.calibrateAndPrecisionRoundOptionsIsPercentual = exports.calibrateAndPrecisionRoundOptionsDefaultPrecision = exports.hasAlreadyProcessedMessage = exports.mapNumberRange = exports.numberWithinRange = exports.toLocalISOString = exports.precisionRound = exports.isLegacyEnabled = void 0; exports.isLightExpose = exports.isNumericExposeFeature = exports.isGroup = exports.isDevice = exports.isEndpoint = exports.assertGroup = exports.assertEndpoint = exports.getFromLookupByValue = void 0; const globalStore = __importStar(require("./store")); const zigbee_herdsman_1 = require("zigbee-herdsman"); const logger_1 = require("./logger"); const NS = 'zhc:utils'; function isLegacyEnabled(options) { return !options.hasOwnProperty('legacy') || options.legacy; } exports.isLegacyEnabled = isLegacyEnabled; function precisionRound(number, precision) { if (typeof precision === 'number') { const factor = Math.pow(10, precision); return Math.round(number * factor) / factor; } else if (typeof precision === 'object') { const thresholds = Object.keys(precision).map(Number).sort((a, b) => b - a); for (const t of thresholds) { if (!isNaN(t) && number >= t) { return precisionRound(number, precision[t]); } } } return number; } exports.precisionRound = precisionRound; function toLocalISOString(dDate) { const tzOffset = -dDate.getTimezoneOffset(); const plusOrMinus = tzOffset >= 0 ? '+' : '-'; const pad = function (num) { const norm = Math.floor(Math.abs(num)); return (norm < 10 ? '0' : '') + norm; }; return dDate.getFullYear() + '-' + pad(dDate.getMonth() + 1) + '-' + pad(dDate.getDate()) + 'T' + pad(dDate.getHours()) + ':' + pad(dDate.getMinutes()) + ':' + pad(dDate.getSeconds()) + plusOrMinus + pad(tzOffset / 60) + ':' + pad(tzOffset % 60); } exports.toLocalISOString = toLocalISOString; function numberWithinRange(number, min, max) { if (number > max) { return max; } else if (number < min) { return min; } else { return number; } } exports.numberWithinRange = numberWithinRange; /** * Maps number from one range to another. In other words it performs a linear interpolation. * Note that this function can interpolate values outside source range (linear extrapolation). * @param value - value to map * @param fromLow - source range lower value * @param fromHigh - source range upper value * @param toLow - target range lower value * @param toHigh - target range upper value * @param number - of decimal places to which result should be rounded * @returns value mapped to new range */ function mapNumberRange(value, fromLow, fromHigh, toLow, toHigh, precision = 0) { const mappedValue = toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); return precisionRound(mappedValue, precision); } exports.mapNumberRange = mapNumberRange; const transactionStore = {}; function hasAlreadyProcessedMessage(msg, model, ID = null, key = null) { if (model.meta && model.meta.publishDuplicateTransaction) return false; const currentID = ID !== null ? ID : msg.meta.zclTransactionSequenceNumber; key = key || msg.device.ieeeAddr; if (transactionStore[key]?.includes(currentID)) return true; // Keep last 5, as they might come in different order: https://github.com/Koenkk/zigbee2mqtt/issues/20024 transactionStore[key] = [currentID, ...(transactionStore[key] ?? [])].slice(0, 5); return false; } exports.hasAlreadyProcessedMessage = hasAlreadyProcessedMessage; exports.calibrateAndPrecisionRoundOptionsDefaultPrecision = { temperature: 2, humidity: 2, pressure: 1, pm25: 0, power: 2, current: 2, current_phase_b: 2, current_phase_c: 2, voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2, energy: 2, device_temperature: 0, soil_moisture: 2, co2: 0, illuminance: 0, illuminance_lux: 0, voc: 0, formaldehyd: 0, co: 0, }; function calibrateAndPrecisionRoundOptionsIsPercentual(type) { return type.startsWith('current') || type.startsWith('energy') || type.startsWith('voltage') || type.startsWith('power') || type.startsWith('illuminance'); } exports.calibrateAndPrecisionRoundOptionsIsPercentual = calibrateAndPrecisionRoundOptionsIsPercentual; function calibrateAndPrecisionRoundOptions(number, options, type) { // Calibrate const calibrateKey = `${type}_calibration`; let calibrationOffset = toNumber(options && options.hasOwnProperty(calibrateKey) ? options[calibrateKey] : 0, calibrateKey); if (calibrateAndPrecisionRoundOptionsIsPercentual(type)) { // linear calibration because measured value is zero based // +/- percent calibrationOffset = number * calibrationOffset / 100; } number = number + calibrationOffset; // Precision round const precisionKey = `${type}_precision`; const defaultValue = exports.calibrateAndPrecisionRoundOptionsDefaultPrecision[type] || 0; const precision = toNumber(options && options.hasOwnProperty(precisionKey) ? options[precisionKey] : defaultValue, precisionKey); return precisionRound(number, precision); } exports.calibrateAndPrecisionRoundOptions = calibrateAndPrecisionRoundOptions; function toPercentage(value, min, max, log = false) { if (value > max) { value = max; } else if (value < min) { value = min; } const normalised = (value - min) / (max - min); return Math.round(normalised * 100); } exports.toPercentage = toPercentage; function addActionGroup(payload, msg, definition) { const disableActionGroup = definition.meta && definition.meta.disableActionGroup; if (!disableActionGroup && msg.groupID) { payload.action_group = msg.groupID; } } exports.addActionGroup = addActionGroup; function getEndpointName(msg, definition, meta) { if (!definition.endpoint) { throw new Error(`Definition '${definition.model}' has not endpoint defined`); } return getKey(definition.endpoint(meta.device), msg.endpoint.ID); } exports.getEndpointName = getEndpointName; function postfixWithEndpointName(value, msg, definition, meta) { // Prevent breaking change https://github.com/Koenkk/zigbee2mqtt/issues/13451 if (!meta) { logger_1.logger.warning(`No meta passed to postfixWithEndpointName, update your external converter!`, NS); // @ts-expect-error meta = { device: null }; } if (definition.meta && definition.meta.multiEndpoint && (!definition.meta.multiEndpointSkip || !definition.meta.multiEndpointSkip.includes(value))) { const endpointName = definition.hasOwnProperty('endpoint') ? getKey(definition.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID; // NOTE: endpointName can be undefined if we have a definition.endpoint and the endpoint is // not listed. if (endpointName) return `${value}_${endpointName}`; } return value; } exports.postfixWithEndpointName = postfixWithEndpointName; function enforceEndpoint(entity, key, meta) { const multiEndpointEnforce = getMetaValue(entity, meta.mapped, 'multiEndpointEnforce', 'allEqual', []); if (multiEndpointEnforce && multiEndpointEnforce.hasOwnProperty(key)) { // @ts-expect-error const endpoint = entity.getDevice().getEndpoint(multiEndpointEnforce[key]); if (endpoint) return endpoint; } return entity; } exports.enforceEndpoint = enforceEndpoint; function getKey(object, value, fallback, convertTo) { for (const key in object) { // @ts-expect-error if (object[key] === value) { return convertTo ? convertTo(key) : key; } } return fallback; } exports.getKey = getKey; function batteryVoltageToPercentage(voltage, option) { let percentage = null; if (option === '3V_2100') { if (voltage < 2100) { percentage = 0; } else if (voltage < 2440) { percentage = 6 - ((2440 - voltage) * 6) / 340; } else if (voltage < 2740) { percentage = 18 - ((2740 - voltage) * 12) / 300; } else if (voltage < 2900) { percentage = 42 - ((2900 - voltage) * 24) / 160; } else if (voltage < 3000) { percentage = 100 - ((3000 - voltage) * 58) / 100; } else if (voltage >= 3000) { percentage = 100; } percentage = Math.round(percentage); } else if (option === '3V_2500') { percentage = toPercentage(voltage, 2500, 3000); } else if (option === '3V_2500_3200') { percentage = toPercentage(voltage, 2500, 3200); } else if (option === '3V_1500_2800') { percentage = 235 - 370000 / (voltage + 1); if (percentage > 100) { percentage = 100; } else if (percentage < 0) { percentage = 0; } percentage = Math.round(percentage); } else if (option === '3V_2850_3000') { percentage = toPercentage(voltage, 2850, 3000); } else if (option === '4LR6AA1_5v') { percentage = toPercentage(voltage, 3000, 4200); } else if (option === '3V_add 1V') { voltage = voltage + 1000; percentage = toPercentage(voltage, 3200, 4200); } else if (option === 'Add_1V_42V_CSM300z2v2') { voltage = voltage + 1000; percentage = toPercentage(voltage, 2900, 4100); // Generic converter that expects an option object with min and max values // I.E. meta: {battery: {voltageToPercentage: {min: 1900, max: 3000}}} } else if (typeof option === 'object') { percentage = toPercentage(voltage, option.min, option.max); } else { throw new Error(`Not batteryVoltageToPercentage type supported: ${option}`); } return percentage; } exports.batteryVoltageToPercentage = batteryVoltageToPercentage; // groupStrategy: allEqual: return only if all members in the groups have the same meta property value. // first: return the first property function getMetaValue(entity, definition, key, groupStrategy = 'first', defaultValue = undefined) { if (isGroup(entity) && entity.members.length > 0) { const values = []; for (let i = 0; i < entity.members.length; i++) { // @ts-expect-error const memberMetaMeta = getMetaValues(definition[i], entity.members[i]); if (memberMetaMeta && memberMetaMeta.hasOwnProperty(key)) { if (groupStrategy === 'first') { // @ts-expect-error return memberMetaMeta[key]; } values.push(memberMetaMeta[key]); } else { values.push(defaultValue); } } if (groupStrategy === 'allEqual' && (new Set(values)).size === 1) { // @ts-expect-error return values[0]; } } else { const definitionMeta = getMetaValues(definition, entity); if (definitionMeta && definitionMeta.hasOwnProperty(key)) { // @ts-expect-error return definitionMeta[key]; } } return defaultValue; } exports.getMetaValue = getMetaValue; function hasEndpoints(device, endpoints) { const eps = device.endpoints.map((e) => e.ID); for (const endpoint of endpoints) { if (!eps.includes(endpoint)) { return false; } } return true; } exports.hasEndpoints = hasEndpoints; function isInRange(min, max, value) { return value >= min && value <= max; } exports.isInRange = isInRange; function replaceInArray(arr, oldElements, newElements, errorIfNotInArray = true) { const clone = [...arr]; for (let i = 0; i < oldElements.length; i++) { const index = clone.indexOf(oldElements[i]); if (index !== -1) { clone[index] = newElements[i]; } else { if (errorIfNotInArray) { throw new Error('Element not in array'); } } } return clone; } exports.replaceInArray = replaceInArray; function filterObject(obj, keys) { const result = {}; for (const [key, value] of Object.entries(obj)) { if (keys.includes(key)) { result[key] = value; } } return result; } exports.filterObject = filterObject; async function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } exports.sleep = sleep; function toSnakeCase(value) { if (typeof value === 'object') { for (const key of Object.keys(value)) { const keySnakeCase = toSnakeCase(key); if (key !== keySnakeCase) { // @ts-expect-error value[keySnakeCase] = value[key]; delete value[key]; } } return value; } else { return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_id'); } } exports.toSnakeCase = toSnakeCase; function toCamelCase(value) { if (typeof value === 'object') { for (const key of Object.keys(value)) { const keyCamelCase = toCamelCase(key); if (key !== keyCamelCase) { // @ts-expect-error value[keyCamelCase] = value[key]; delete value[key]; } } return value; } else { return value.replace(/_([a-z])/g, (x, y) => y.toUpperCase()); } } exports.toCamelCase = toCamelCase; function getLabelFromName(name) { const label = name.replace(/_/g, ' '); return label[0].toUpperCase() + label.slice(1); } exports.getLabelFromName = getLabelFromName; function saveSceneState(entity, sceneID, groupID, state, name) { const attributes = ['state', 'brightness', 'color', 'color_temp', 'color_mode']; if (!entity.meta.hasOwnProperty('scenes')) entity.meta.scenes = {}; const metaKey = `${sceneID}_${groupID}`; entity.meta.scenes[metaKey] = { name, state: filterObject(state, attributes) }; entity.save(); } exports.saveSceneState = saveSceneState; function deleteSceneState(entity, sceneID = null, groupID = null) { if (entity.meta.scenes) { if (sceneID == null && groupID == null) { entity.meta.scenes = {}; } else { const metaKey = `${sceneID}_${groupID}`; if (entity.meta.scenes.hasOwnProperty(metaKey)) { delete entity.meta.scenes[metaKey]; } } entity.save(); } } exports.deleteSceneState = deleteSceneState; function getSceneState(entity, sceneID, groupID) { const metaKey = `${sceneID}_${groupID}`; if (entity.meta.hasOwnProperty('scenes') && entity.meta.scenes.hasOwnProperty(metaKey)) { return entity.meta.scenes[metaKey].state; } return null; } exports.getSceneState = getSceneState; function getEntityOrFirstGroupMember(entity) { if (isGroup(entity)) { return entity.members.length > 0 ? entity.members[0] : null; } else { return entity; } } exports.getEntityOrFirstGroupMember = getEntityOrFirstGroupMember; function getTransition(entity, key, meta) { const { options, message } = meta; let manufacturerIDs = []; if (isGroup(entity)) { manufacturerIDs = entity.members.map((m) => m.getDevice().manufacturerID); } else if (isEndpoint(entity)) { manufacturerIDs = [entity.getDevice().manufacturerID]; } if (manufacturerIDs.includes(4476)) { /** * When setting both brightness and color temperature with a transition, the brightness is skipped * for IKEA TRADFRI bulbs. * To workaround this we skip the transition for the brightness as it is applied first. * https://github.com/Koenkk/zigbee2mqtt/issues/1810 */ if (key === 'brightness' && (message.hasOwnProperty('color') || message.hasOwnProperty('color_temp'))) { return { time: 0, specified: false }; } } if (message.hasOwnProperty('transition')) { const time = toNumber(message.transition, 'transition'); return { time: time * 10, specified: true }; } else if (options.hasOwnProperty('transition') && options.transition !== '') { const transition = toNumber(options.transition, 'transition'); return { time: transition * 10, specified: true }; } else { return { time: 0, specified: false }; } } exports.getTransition = getTransition; function getOptions(definition, entity, options = {}) { const allowed = ['disableDefaultResponse', 'timeout']; return getMetaValues(definition, entity, allowed, options); } exports.getOptions = getOptions; function getMetaValues(definitions, entity, allowed, options = {}) { const result = { ...options }; for (const definition of Array.isArray(definitions) ? definitions : [definitions]) { if (definition && definition.meta) { for (const key of Object.keys(definition.meta)) { if (allowed == null || allowed.includes(key)) { // @ts-expect-error const value = definition.meta[key]; if (typeof value === 'function') { if (isEndpoint(entity)) { result[key] = value(entity); } } else { result[key] = value; } } } } } return result; } exports.getMetaValues = getMetaValues; function getObjectProperty(object, key, defaultValue) { return object && object.hasOwnProperty(key) ? object[key] : defaultValue; } exports.getObjectProperty = getObjectProperty; function validateValue(value, allowed) { if (!allowed.includes(value)) { throw new Error(`'${value}' not allowed, choose between: ${allowed}`); } } exports.validateValue = validateValue; async function getClusterAttributeValue(endpoint, cluster, attribute, fallback = undefined) { try { if (endpoint.getClusterAttributeValue(cluster, attribute) == null) { await endpoint.read(cluster, [attribute]); } return endpoint.getClusterAttributeValue(cluster, attribute); } catch (error) { if (fallback !== undefined) return fallback; throw error; } } exports.getClusterAttributeValue = getClusterAttributeValue; function normalizeCelsiusVersionOfFahrenheit(value) { const fahrenheit = (value * 1.8) + 32; const roundedFahrenheit = Number((Math.round(Number((fahrenheit * 2).toFixed(1))) / 2).toFixed(1)); return Number(((roundedFahrenheit - 32) / 1.8).toFixed(2)); } exports.normalizeCelsiusVersionOfFahrenheit = normalizeCelsiusVersionOfFahrenheit; function noOccupancySince(endpoint, options, publish, action) { if (options && options.no_occupancy_since) { if (action == 'start') { globalStore.getValue(endpoint, 'no_occupancy_since_timers', []).forEach((t) => clearTimeout(t)); globalStore.putValue(endpoint, 'no_occupancy_since_timers', []); options.no_occupancy_since.forEach((since) => { const timer = setTimeout(() => { publish({ no_occupancy_since: since }); }, since * 1000); globalStore.getValue(endpoint, 'no_occupancy_since_timers').push(timer); }); } else if (action === 'stop') { globalStore.getValue(endpoint, 'no_occupancy_since_timers', []).forEach((t) => clearTimeout(t)); globalStore.putValue(endpoint, 'no_occupancy_since_timers', []); } } } exports.noOccupancySince = noOccupancySince; function attachOutputCluster(device, clusterKey) { const clusterId = zigbee_herdsman_1.Zcl.Utils.getCluster(clusterKey, device.manufacturerID, device.customClusters).ID; const endpoint = device.getEndpoint(1); if (!endpoint.outputClusters.includes(clusterId)) { endpoint.outputClusters.push(clusterId); device.save(); } } exports.attachOutputCluster = attachOutputCluster; function printNumberAsHex(value, hexLength) { const hexValue = value.toString(16).padStart(hexLength, '0'); return `0x${hexValue}`; } exports.printNumberAsHex = printNumberAsHex; function printNumbersAsHexSequence(numbers, hexLength) { return numbers.map((v) => v.toString(16).padStart(hexLength, '0')).join(':'); } exports.printNumbersAsHexSequence = printNumbersAsHexSequence; // eslint-disable-next-line function assertObject(value, property) { const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null; if (!isObject) { throw new Error(`${property} is not a object, got ${typeof value} (${JSON.stringify(value)})`); } } exports.assertObject = assertObject; function assertArray(value, property) { property = property ? `'${property}'` : 'Value'; if (!Array.isArray(value)) throw new Error(`${property} is not an array, got ${typeof value} (${value.toString()})`); } exports.assertArray = assertArray; function assertString(value, property) { property = property ? `'${property}'` : 'Value'; if (typeof value !== 'string') throw new Error(`${property} is not a string, got ${typeof value} (${value.toString()})`); } exports.assertString = assertString; function isNumber(value) { return typeof value === 'number'; } exports.isNumber = isNumber; // eslint-disable-next-line function isObject(value) { return typeof value === 'object' && !Array.isArray(value); } exports.isObject = isObject; function isString(value) { return typeof value === 'string'; } exports.isString = isString; function isBoolean(value) { return typeof value === 'boolean'; } exports.isBoolean = isBoolean; function assertNumber(value, property) { property = property ? `'${property}'` : 'Value'; if (typeof value !== 'number' || Number.isNaN(value)) throw new Error(`${property} is not a number, got ${typeof value} (${value?.toString()})`); } exports.assertNumber = assertNumber; function toNumber(value, property) { property = property ? `'${property}'` : 'Value'; // @ts-ignore const result = parseFloat(value); if (Number.isNaN(result)) { throw new Error(`${property} is not a number, got ${typeof value} (${value.toString()})`); } return result; } exports.toNumber = toNumber; function getFromLookup(value, lookup, defaultValue = undefined, keyIsBool = false) { let result = undefined; if (!keyIsBool) { if (typeof value === 'string') { result = lookup[value] ?? lookup[value.toLowerCase()] ?? lookup[value.toUpperCase()]; } else if (typeof value === 'number') { result = lookup[value]; } else { throw new Error(`Expected string or number, got: ${typeof value}`); } } else { // Silly hack, but boolean is not supported as index if (typeof value === 'boolean') { const stringValue = value.toString(); result = (lookup[stringValue] ?? lookup[stringValue.toLowerCase()] ?? lookup[stringValue.toUpperCase()]); } else { throw new Error(`Expected boolean, got: ${typeof value}`); } } if (result === undefined && defaultValue === undefined) { throw new Error(`Value: '${value}' not found in: [${Object.keys(lookup).join(', ')}]`); } return result ?? defaultValue; } exports.getFromLookup = getFromLookup; function getFromLookupByValue(value, lookup, defaultValue = undefined) { for (const entry of Object.entries(lookup)) { if (entry[1] === value) { return entry[0]; } } if (defaultValue === undefined) { throw new Error(`Expected one of: ${Object.values(lookup).join(', ')}, got: '${value}'`); } return defaultValue; } exports.getFromLookupByValue = getFromLookupByValue; function assertEndpoint(obj) { if (obj?.constructor?.name?.toLowerCase() !== 'endpoint') throw new Error('Not an endpoint'); } exports.assertEndpoint = assertEndpoint; function assertGroup(obj) { if (obj?.constructor?.name?.toLowerCase() !== 'group') throw new Error('Not a group'); } exports.assertGroup = assertGroup; function isEndpoint(obj) { return obj.constructor.name.toLowerCase() === 'endpoint'; } exports.isEndpoint = isEndpoint; function isDevice(obj) { return obj.constructor.name.toLowerCase() === 'device'; } exports.isDevice = isDevice; function isGroup(obj) { return obj.constructor.name.toLowerCase() === 'group'; } exports.isGroup = isGroup; function isNumericExposeFeature(feature) { return feature?.type === 'numeric'; } exports.isNumericExposeFeature = isNumericExposeFeature; function isLightExpose(expose) { return expose?.type === 'light'; } exports.isLightExpose = isLightExpose; exports.noOccupancySince = noOccupancySince; exports.getOptions = getOptions; exports.isLegacyEnabled = isLegacyEnabled; exports.precisionRound = precisionRound; exports.toLocalISOString = toLocalISOString; exports.numberWithinRange = numberWithinRange; exports.mapNumberRange = mapNumberRange; exports.hasAlreadyProcessedMessage = hasAlreadyProcessedMessage; exports.calibrateAndPrecisionRoundOptions = calibrateAndPrecisionRoundOptions; exports.calibrateAndPrecisionRoundOptionsIsPercentual = calibrateAndPrecisionRoundOptionsIsPercentual; exports.calibrateAndPrecisionRoundOptionsDefaultPrecision = exports.calibrateAndPrecisionRoundOptionsDefaultPrecision; exports.toPercentage = toPercentage; exports.addActionGroup = addActionGroup; exports.postfixWithEndpointName = postfixWithEndpointName; exports.enforceEndpoint = enforceEndpoint; exports.getKey = getKey; exports.getObjectProperty = getObjectProperty; exports.batteryVoltageToPercentage = batteryVoltageToPercentage; exports.getEntityOrFirstGroupMember = getEntityOrFirstGroupMember; exports.getTransition = getTransition; exports.getMetaValue = getMetaValue; exports.validateValue = validateValue; exports.hasEndpoints = hasEndpoints; exports.isInRange = isInRange; exports.replaceInArray = replaceInArray; exports.filterObject = filterObject; exports.saveSceneState = saveSceneState; exports.sleep = sleep; exports.toSnakeCase = toSnakeCase; exports.toCamelCase = toCamelCase; exports.getLabelFromName = getLabelFromName; exports.normalizeCelsiusVersionOfFahrenheit = normalizeCelsiusVersionOfFahrenheit; exports.deleteSceneState = deleteSceneState; exports.getSceneState = getSceneState; exports.attachOutputCluster = attachOutputCluster; exports.printNumberAsHex = printNumberAsHex; exports.printNumbersAsHexSequence = printNumbersAsHexSequence; exports.getFromLookup = getFromLookup; //# sourceMappingURL=utils.js.map