/** * Copyright 2018-2021 bluefox <dogafox@gmail.com> * * The MIT License (MIT) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. **/ // Version 1.0.13, 2021.06.27 // Keep this file ES5 conform! No const, let, not lambdas => , no ..., no default values for arguments, no let [arg] = abc() and other modern stuff. // eslint-disable-next-line 'use strict'; var Types = { unknown: 'unknown', airCondition: 'airCondition', blind: 'blind', blindButtons: 'blindButtons', button: 'button', buttonSensor: 'buttonSensor', camera: 'camera', url: 'url', chart: 'chart', image: 'image', dimmer: 'dimmer', door: 'door', fireAlarm: 'fireAlarm', floodAlarm: 'floodAlarm', gate: 'gate', humidity: 'humidity', info: 'info', instance: 'instance', light: 'light', lock: 'lock', location: 'location', media: 'media', motion: 'motion', rgb: 'rgb', ct: 'ct', rgbSingle: 'rgbSingle', hue: 'hue', slider: 'slider', socket: 'socket', temperature: 'temperature', thermostat: 'thermostat', valve: 'valve', volume: 'volume', vacuumCleaner: 'vacuumCleaner', volumeGroup: 'volumeGroup', window: 'window', windowTilt: 'windowTilt', weatherCurrent: 'weatherCurrent', weatherForecast: 'weatherForecast', warning: 'warning' }; var SharedPatterns = { working: {role: /^indicator\.working$/, indicator: true, notSingle: true, name: 'WORKING', required: false, defaultRole: 'indicator.working', defaultType: 'boolean'}, unreach: {role: /^indicator(\.maintenance)?\.unreach$/, indicator: true, type: 'boolean', notSingle: true, name: 'UNREACH', required: false, defaultRole: 'indicator.maintenance.unreach'}, lowbat: {role: /^indicator(\.maintenance)?\.lowbat$|^indicator(\.maintenance)?\.battery$/, indicator: true, type: 'boolean', notSingle: true, name: 'LOWBAT', required: false, defaultRole: 'indicator.maintenance.lowbat'}, maintain: {role: /^indicator\.maintenance$/, indicator: true, type: 'boolean', notSingle: true, name: 'MAINTAIN', required: false, defaultRole: 'indicator.maintenance'}, error: {role: /^indicator\.error$/, indicator: true, notSingle: true, name: 'ERROR', required: false, defaultRole: 'indicator.error', defaultType: 'string'}, direction: {role: /^indicator\.direction$/, indicator: true, notSingle: true, name: 'DIRECTION', required: false, defaultRole: 'indicator.direction'}, reachable: {role: /^indicator\.reachable$/, indicator: true, type: 'boolean', notSingle: true, name: 'CONNECTED', required: false, defaultRole: 'indicator.reachable', inverted: true}, }; // Description of flags // role - RegEx to detect role // channelRole - RegEx to detect channel role of state // ignoreRole - RegEx to ignore some specific roles // indicator - is it will be shown like small icon or as a value // type - state type: 'number', 'string' or 'boolean' or array of possible values // name - own TAG of the state to process it in the logic // write - if set to true or false, it will be checked the write attribute, if no attribute, so "false" will be assumed // read - if set to true or false, it will be checked the write attribute, if no attribute, so "true" will be assumed // min - type of attribute: number', 'string' or 'boolean'. This attribute must exists in common // max - type of attribute: number', 'string' or 'boolean'. This attribute must exists in common // required - if required to detect the pattern as valid // noSubscribe - no automatic subscription for this state (e.g if write only) // searchInParent - if this pattern should be search in device too and not only in channel // enums - function to execute custom category detection // multiple - if more than one state may have this pattern in channel // noDeviceDetection - do not search indicators in parent device // notSingle - this state may belong to more than one tile simultaneously (e.g. volume tile and media with volume) // inverted - is state of indicator must be inverted // stateName - regex for state names (IDs). Not suggested // defaultStates - is for detection irrelevant, but will be used by iobroker.devices. // defaultRole - is for detection irrelevant, but will be used by iobroker.devices. // defaultUnit - is for detection irrelevant, but will be used by iobroker.devices. // defaultType - is for detection irrelevant, but will be used by iobroker.devices. function ChannelDetector() { if (!(this instanceof ChannelDetector)) { return new ChannelDetector(); } var patterns = { chart: { states: [ {objectType: 'chart', name: 'CHART'} ], type: Types.chart }, mediaPlayer: { // receive the state of player via media.state. Controlling of the player via buttons states: [ // one of {role: /^media.state(\..*)?$/, indicator: false, type: ['boolean', 'number'], name: 'STATE', required: true, defaultRole: 'media.state'}, // optional {role: /^button.play(\..*)?$|^action.play(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'PLAY', required: false, noSubscribe: true, defaultRole: 'button.play'}, {role: /^button.pause(\..*)?$|^action.pause(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'PAUSE', required: false, noSubscribe: true, defaultRole: 'button.pause'}, {role: /^button.stop(\..*)?$|^action.stop(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'STOP', required: false, noSubscribe: true, defaultRole: 'button.stop'}, {role: /^button.next(\..*)?$|^action.next(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'NEXT', required: false, noSubscribe: true, defaultRole: 'button.next'}, {role: /^button.prev(\..*)?$|^action.prev(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'PREV', required: false, noSubscribe: true, defaultRole: 'button.prev'}, {role: /^media.mode.shuffle(\..*)?$/, indicator: false, write: true, type: 'boolean', name: 'SHUFFLE', required: false, noSubscribe: true, defaultRole: 'media.mode.shuffle'}, {role: /^media.mode.repeat(\..*)?$/, indicator: false, write: true, type: 'number', name: 'REPEAT', required: false, noSubscribe: true, defaultRole: 'media.mode.repeat'}, {role: /^media.artist(\..*)?$/, indicator: false, write: false, type: 'string', name: 'ARTIST', required: false, defaultRole: 'media.artist'}, {role: /^media.album(\..*)?$/, indicator: false, write: false, type: 'string', name: 'ALBUM', required: false, defaultRole: 'media.album'}, {role: /^media.title(\..*)?$/, indicator: false, write: false, type: 'string', name: 'TITLE', required: false, defaultRole: 'media.title'}, // one of following [ {role: /^media.cover$|^media.cover.big$/, indicator: false, write: false, type: 'string', name: 'COVER', required: false, notSingle: true, defaultRole: 'media.cover'}, {role: /^media.cover(\..*)$/, indicator: false, write: false, type: 'string', name: 'COVER', required: false, notSingle: true}, ], {role: /^media.duration(\..*)?$/, indicator: false, write: false, type: 'number', name: 'DURATION', required: false, noSubscribe: true, defaultRole: 'media.duration', defaultUnit: 'sec'}, {role: /^media.elapsed(\..*)?$/, indicator: false, type: 'number', name: 'ELAPSED', required: false, noSubscribe: true, defaultRole: 'media.elapsed', defaultUnit: 'sec'}, {role: /^media.seek(\..*)?$/, indicator: false, write: true, type: 'number', name: 'SEEK', required: false, noSubscribe: true, defaultRole: 'media.seek'}, {role: /^media.track(\..*)?$/, indicator: false, type: 'string', name: 'TRACK', required: false, noSubscribe: true, defaultRole: 'media.track'}, {role: /^media.episode(\..*)?$/, indicator: false, type: 'string', name: 'EPISODE', required: false, noSubscribe: true, defaultRole: 'media.episode'}, {role: /^media.season(\..*)?$/, indicator: false, type: 'string', name: 'SEASON', required: false, noSubscribe: true, defaultRole: 'media.season'}, {role: /^level.volume?$/, indicator: false, type: 'number', min: 'number', max: 'number', write: true, name: 'VOLUME', required: false, notSingle: true, noSubscribe: true, defaultRole: 'level.volume'}, {role: /^value.volume?$/, indicator: false, type: 'number', min: 'number', max: 'number', write: false, name: 'VOLUME_ACTUAL', required: false, notSingle: true, noSubscribe: true, defaultRole: 'value.volume'}, {role: /^media.mute?$/, indicator: false, type: 'boolean', write: true, name: 'MUTE', required: false, notSingle: true, noSubscribe: true, defaultRole: 'media.mute'}, // Ignore following states of chromecast {stateName: /\.paused$|\.playerState$/, indicator: false, name: 'IGNORE', required: false, multiple: true, noSubscribe: true}, SharedPatterns.reachable, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.media }, weatherForecast : { states: [ {role: /^weather.icon$|^weather.icon.forecast.0$/, indicator: false, type: 'string', name: 'ICON', required: true, defaultRole: 'weather.icon.forecast.0'}, {role: /^value.temperature.min.forecast.0$/, indicator: false, type: 'number', name: 'TEMP_MIN', required: true, defaultRole: 'value.temperature.min.forecast.0'}, {role: /^value.temperature.max.forecast.0$/, indicator: false, type: 'number', name: 'TEMP_MAX', required: true, defaultRole: 'value.temperature.max.forecast.0'}, // optional {role: /^value.precipitation$|^value.precipitation.forecast.0$/, indicator: false, type: 'number', name: 'PRECIPITATION_CHANCE', unit: '%', required: false, defaultRole: 'value.precipitation.forecast.0'}, {role: /^value.precipitation$|^value.precipitation.forecast.0$/, indicator: false, type: 'number', name: 'PRECIPITATION', unit: 'mm', required: false, defaultRole: 'value.precipitation.forecast.0'}, {role: /^date$|^date.forecast.0$/, indicator: false, type: 'string', name: 'DATE', required: false, defaultRole: 'date.forecast.0'}, {role: /^dayofweek$|^dayofweek.forecast.0$/, indicator: false, type: 'string', name: 'DOW', required: false, defaultRole: 'dayofweek.forecast.0'}, {role: /^weather.state$|^weather.state.forecast.0$/, indicator: false, type: 'string', name: 'STATE', required: false, defaultRole: 'weather.state.forecast.0'}, {role: /^value.temperature$|^value.temperature.forecast.0$/, indicator: false, type: 'number', name: 'TEMP', required: false, defaultRole: 'value.temperature.forecast.0'}, {role: /^value.pressure$/, indicator: false, type: 'number', name: 'PRESSURE', required: false, defaultRole: 'weather.icon.forecast.0'}, {role: /^value.humidity$|value.humidity.forecast.0$/, indicator: false, type: 'number', name: 'HUMIDITY', required: false, defaultRole: 'value.humidity.forecast.0'}, {role: /^time.sunrise$|^time.sunrise.forecast.0$/, indicator: false, type: 'string', name: 'TIME_SUNRISE', required: false, defaultRole: 'time.sunrise'}, {role: /^time.sunset$|^time.sunset.forecast.0$/, indicator: false, type: 'string', name: 'TIME_SUNSET', required: false, defaultRole: 'time.sunset'}, {role: /^value.temperature.windchill$|^value.temperature.windchill.forecast.0$/, indicator: false, type: 'number', name: 'WIND_CHILL', required: false, defaultRole: 'value.temperature.windchill.forecast.0'}, {role: /^value.temperature.feelslike$|^value.temperature.feelslike.forecast.0$/, indicator: false, type: 'number', name: 'FEELS_LIKE', required: false, defaultRole: 'value.temperature.feelslike.forecast.0'}, {role: /^value.speed.wind$|^value.speed.wind.forecast.0$/, indicator: false, type: 'number', name: 'WIND_SPEED', required: false, defaultRole: 'value.speed.wind.forecast.0'}, {role: /^value.direction.wind$|^value.direction.wind.forecast.0$/, indicator: false, type: 'number', name: 'WIND_DIRECTION',required: false, defaultRole: 'value.direction.wind.forecast.0'}, {role: /^weather.direction.wind$|^weather.direction.wind.forecast.0$/, indicator: false, type: 'string', name: 'WIND_DIRECTION_STR',required: false, defaultRole: 'weather.direction.wind.forecast.0'}, {role: /^weather.icon.wind$|^weather.icon.wind.forecast.0$/, indicator: false, type: 'string', name: 'WIND_ICON', required: false, defaultRole: 'weather.icon.wind.forecast.0'}, {role: /^weather.chart.url$/, indicator: false, type: 'string', name: 'HISTORY_CHART', required: false, noSubscribe: true, defaultRole: 'weather.chart.url'}, {role: /^weather.chart.url.forecast$/, indicator: false, type: 'string', name: 'FORECAST_CHART', required: false, noSubscribe: true, defaultRole: 'weather.chart.url.forecast'}, {role: /^location$/, indicator: false, type: 'string', name: 'LOCATION', required: false, multiple: true, defaultRole: 'location'}, // other days {role: /^weather.icon.forecast.(\d)$/, indicator: false, type: 'string', name: 'ICON%d', required: false, searchInParent: true, multiple: true, noSubscribe: true, notSingle: true}, {role: /^value.temperature.min.forecast.(\d)$/, indicator: false, type: 'number', name: 'TEMP_MIN%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.temperature.max.forecast.(\d)$/, indicator: false, type: 'number', name: 'TEMP_MAX%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^date.forecast.(\d)$/, indicator: false, type: 'string', name: 'DATE%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^dayofweek.forecast.(\d)$/, indicator: false, type: 'string', name: 'DOW%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^weather.state.forecast.(\d)$/, indicator: false, type: 'string', name: 'STATE%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.temperature.forecast.(\d)$/, indicator: false, type: 'number', name: 'TEMP%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.humidity.forecast.(\d)$/, indicator: false, type: 'number', name: 'HUMIDITY%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.humidity.max.forecast.(\d)$/, indicator: false, type: 'number', name: 'HUMIDITY_MAX%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.precipitation.forecast.(\d)$/, indicator: false, type: 'number', unit: '%', name: 'PRECIPITATION_CHANCE%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.precipitation.forecast.(\d)$/, indicator: false, type: 'number', unit: 'mm', name: 'PRECIPITATION%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.speed.wind.forecast.(\d)$/, indicator: false, type: 'number', name: 'WIND_SPEED%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^value.direction.wind.forecast.(\d)$/, indicator: false, type: 'number', name: 'WIND_DIRECTION%d',required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^weather.direction.wind.forecast.(\d)$/, indicator: false, type: 'string', name: 'WIND_DIRECTION_STR%d',required: false, searchInParent: true, multiple: true, noSubscribe: true}, {role: /^weather.icon.wind.forecast.(\d)$/, indicator: false, type: 'string', name: 'WIND_ICON%d', required: false, searchInParent: true, multiple: true, noSubscribe: true}, ], type: Types.weatherForecast }, rgb: { states: [ {role: /^level\.color\.red$/, indicator: false, type: 'number', write: true, name: 'RED', required: true, defaultRole: 'level.color.red'}, {role: /^level\.color\.green$/, indicator: false, type: 'number', write: true, name: 'GREEN', required: true, defaultRole: 'level.color.green'}, {role: /^level\.color\.blue$/, indicator: false, type: 'number', write: true, name: 'BLUE', required: true, defaultRole: 'level.color.blue'}, // optional {role: /^level\.color\.white$/, indicator: false, type: 'number', write: true, name: 'WHITE', required: false, defaultRole: 'level.color.white'}, {role: /^level\.dimmer$/, indicator: false, type: 'number', write: true, name: 'DIMMER', required: false, defaultRole: 'level.dimmer', defaultUnit: '%'}, {role: /^level\.brightness$/, indicator: false, type: 'number', write: true, name: 'BRIGHTNESS', required: false}, {role: /^level\.color\.saturation$/, indicator: false, type: 'number', write: true, name: 'SATURATION', required: false}, {role: /^level\.color\.temperature$/, indicator: false, type: 'number', write: true, name: 'TEMPERATURE', required: false, defaultRole: 'level.color.temperature', defaultUnit: '°K'}, {role: /^switch\.light$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^switch$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^state(\.light)?$/, indicator: false, type: 'boolean', write: false, name: 'ON_ACTUAL', required: false, defaultRole: 'state.light'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.rgb }, // remove it when all adapters fixed (2018.08.15) r=>red, g=>green, b=>blue rgbOld: { states: [ {role: /^level\.color\.r$/, indicator: false, type: 'number', write: true, name: 'RED', required: true}, {role: /^level\.color\.g$/, indicator: false, type: 'number', write: true, name: 'GREEN', required: true}, {role: /^level\.color\.b$/, indicator: false, type: 'number', write: true, name: 'BLUE', required: true}, // optional {role: /^level\.dimmer$/, indicator: false, type: 'number', write: true, name: 'DIMMER', required: false}, {role: /^level\.brightness$/, indicator: false, type: 'number', write: true, name: 'BRIGHTNESS', required: false}, {role: /^level\.color\.saturation$/, indicator: false, type: 'number', write: true, name: 'SATURATION', required: false}, {role: /^level\.color\.temperature$/, indicator: false, type: 'number', write: true, name: 'TEMPERATURE', required: false, defaultUnit: '°K'}, {role: /^switch\.light$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^switch$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.rgb }, rgbSingle: { states: [ {role: /^level\.color\.rgb$/, indicator: false, type: 'string', write: true, name: 'RGB', required: true, defaultRole: 'level.color.rgb'}, // optional {role: /^level\.dimmer$/, indicator: false, type: 'number', write: true, name: 'DIMMER', required: false, defaultRole: 'level.dimmer'}, {role: /^level\.brightness$/, indicator: false, type: 'number', write: true, name: 'BRIGHTNESS', required: false, defaultUnit: '%'}, {role: /^level\.color\.saturation$/, indicator: false, type: 'number', write: true, name: 'SATURATION', required: false}, {role: /^level\.color\.temperature$/, indicator: false, type: 'number', write: true, name: 'TEMPERATURE', required: false, defaultRole: 'level.color.temperature', defaultUnit: '°K'}, {role: /^switch\.light$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^switch$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^state(\.light)?$/, indicator: false, type: 'boolean', write: false, name: 'ON_ACTUAL', required: false, defaultRole: 'state.light'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.rgbSingle }, hue: { states: [ {role: /^level\.color\.hue$/, indicator: false, type: 'number', write: true, name: 'HUE', required: true, defaultRole: 'level.color.hue', defaultUnit: '°'}, // optional {role: /^level\.dimmer$/, indicator: false, type: 'number', write: true, name: 'DIMMER', required: false, searchInParent: true, defaultRole: 'level.dimmer', defaultUnit: '°C'}, {role: /^level\.brightness$/, indicator: false, type: 'number', write: true, name: 'BRIGHTNESS', required: false}, {role: /^level\.color\.saturation$/, indicator: false, type: 'number', write: true, name: 'SATURATION', required: false}, {role: /^level\.color\.temperature$/, indicator: false, type: 'number', write: true, name: 'TEMPERATURE', required: false, defaultRole: 'level.color.temperature', defaultUnit: '°K'}, {role: /^switch\.light$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^switch$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^state(\.light)?$/, indicator: false, type: 'boolean', write: false, name: 'ON_ACTUAL', required: false, defaultRole: 'state.light'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.hue }, ct: { states: [ {role: /^level\.color\.temperature$/, indicator: false, type: 'number', write: true, name: 'TEMPERATURE', required: true, defaultRole: 'level.color.temperature', defaultUnit: '°K'}, // optional {role: /^level\.dimmer$/, indicator: false, type: 'number', write: true, name: 'DIMMER', required: false, defaultRole: 'level.dimmer', defaultUnit: '%'}, {role: /^level\.brightness$/, indicator: false, type: 'number', write: true, name: 'BRIGHTNESS', required: false}, {role: /^level\.color\.saturation$/, indicator: false, type: 'number', write: true, name: 'SATURATION', required: false}, {role: /^switch\.light$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false, defaultRole: 'switch.light'}, {role: /^switch$/, indicator: false, type: 'boolean', write: true, name: 'ON', required: false}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.ct }, warning: { states: [ {role: /^value\.warning$/, indicator: false, name: 'LEVEL', required: true, defaultRole: 'value.warning'}, // optional {role: /^weather\.title\.short$/, indicator: false, type: 'string', name: 'TITLE', required: false, defaultRole: 'weather.title.short'}, {role: /^weather\.title$/, indicator: false, type: 'string', name: 'INFO', required: false, defaultRole: 'weather.title'}, {role: /^date\.start$/, indicator: false, type: 'string', name: 'START', required: false, defaultRole: 'date.start'}, {role: /^date\.end$/, indicator: false, type: 'string', name: 'END', required: false, defaultRole: 'date.end'}, {role: /^date$/, indicator: false, type: 'string', name: 'START', required: false}, {role: /^weather\.chart\.url/, indicator: false, type: 'string', name: 'ICON', required: false, defaultRole: 'weather.chart.url'}, // For detailed screen {role: /^weather\.state$/, indicator: false, type: 'string', name: 'DESC', required: false, noSubscribe: true, defaultRole: 'weather.state'}, ], type: Types.warning }, // most full description could be found here: https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/device-type-thermostat-ac-docpage/ airCondition: { states: [ {role: /temperature(\..*)?$/, indicator: false, write: true, type: 'number', name: 'SET', required: true, defaultRole: 'level.temperature', defaultUnit: '°C'}, // AUTO, COOL, HEAT, ECO, OFF, DRY, FAN_ONLY {role: /airconditioner$/, indicator: false, write: true, type: 'number', searchInParent: true, name: 'MODE', required: true, defaultRole: 'level.mode.airconditioner', defaultStates: {0: 'OFF', 1: 'AUTO', 2: 'COOL', 3: 'HEAT', 4: 'ECO', 5: 'FAN_ONLY', 6: 'DRY'}}, // optional {role: /(speed|mode)\.fan$/, indicator: false, write: true, type: 'number', name: 'SPEED', required: false, defaultRole: 'level.mode.fan', defaultStates: {0: 'AUTO', 1: 'HIGH', 2: 'LOW', 3: 'MEDIUM', 4: 'QUIET', 5: 'TURBO'}}, {role: /^switch\.power$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'POWER', required: false, defaultRole: 'switch.power'}, {role: /^switch$/, indicator: false, write: true, type: 'boolean', searchInParent: true, name: 'POWER', required: false}, {role: /temperature(\..*)?$/, indicator: false, write: false, type: 'number', searchInParent: true, name: 'ACTUAL', required: false, defaultRole: 'value.temperature', defaultUnit: '°C'}, {role: /humidity(\..*)?$/, indicator: false, write: false, type: 'number', searchInParent: true, name: 'HUMIDITY', required: false, defaultRole: 'value.humidity', defaultUnit: '%'}, {role: /^switch\.boost(\..*)?$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'BOOST', required: false, defaultRole: 'switch.boost'}, {role: /swing$/, indicator: false, write: true, type: 'number', searchInParent: true, name: 'SWING', required: false, defaultRole: 'level.mode.swing', defaultStates: {0: 'AUTO', 1: 'HORIZONTAL', 2: 'STATIONARY', 3: 'VERTICAL'}}, {role: /swing$/, indicator: false, write: true, type: 'boolean', searchInParent: true, name: 'SWING', required: false, defaultRole: 'switch.mode.swing'}, SharedPatterns.unreach, SharedPatterns.maintain, SharedPatterns.error ], type: Types.airCondition }, thermostat: { states: [ {role: /temperature(\..*)?$/, indicator: false, write: true, type: 'number', name: 'SET', required: true, defaultRole: 'level.temperature', defaultUnit: '°C'}, // optional {role: /temperature(\..*)?$/, indicator: false, write: false, type: 'number', searchInParent: true, name: 'ACTUAL', required: false, defaultRole: 'value.temperature', defaultUnit: '°C'}, {role: /humidity(\..*)?$/, indicator: false, write: false, type: 'number', searchInParent: true, name: 'HUMIDITY', required: false, defaultRole: 'value.humidity', defaultUnit: '%'}, {role: /^switch(\.mode)?\.boost(\..*)?$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'BOOST', required: false, defaultRole: 'switch.mode.boost'}, {role: /^switch\.power$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'POWER', required: false, defaultRole: 'switch.power'}, {role: /^switch(\.mode)?\.party$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'PARTY', required: false, defaultRole: 'switch.mode.party'}, {role: /^switch$/, indicator: false, write: true, type: 'boolean', searchInParent: true, name: 'POWER', required: false}, {role: /^level(\.mode)?\.thermostat$/, indicator: false, write: true, type: 'number', searchInParent: true, name: 'MODE', required: false, defaultRole: 'level.mode.thermostat', defaultStates: {0: 'AUTO', 1: 'MANUAL'}}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.thermostat }, vacuumCleaner: { states: [ {role: /^switch\.power$/, indicator: false, write: true, type: ['boolean', 'number'], searchInParent: true, name: 'POWER', required: true, defaultRole: 'switch.power'}, // AUTO, ECO, EXPRESS, NORMAL, QUIET {role: /mode\.cleanup$/, indicator: false, write: true, type: 'number', searchInParent: true, name: 'MODE', required: true, defaultRole: 'level.mode.cleanup', defaultStates: {0: 'AUTO', 1: 'NORMAL', 2: 'QUIET', 3: 'ECO', 4: 'EXPRESS'}}, // optional {role: /vacuum\.map\.base64$/, indicator: false, write: false, type: 'string', searchInParent: true, name: 'MAP_BASE64', required: false, defaultRole: 'vacuum.map.base64'}, {role: /vacuum\.map\.url$/, indicator: false, write: false, type: 'string', searchInParent: true, name: 'MAP_URL', required: false}, {role: /mode\.work$/, indicator: false, write: true, type: 'number', searchInParent: true, name: 'WORK_MODE', required: false, defaultRole: 'level.mode.work', defaultStates: {0: 'AUTO', 1: 'FAST', 2: 'MEDIUM', 3: 'SLOW', 4: 'TURBO'}}, {role: /^value\.water$/, indicator: false, write: false, type: 'number', searchInParent: true, unit: '%', name: 'WATER', required: false, defaultRole: 'value.water', defaultUnit: '%'}, {role: /^value\.waste$/, indicator: false, write: false, type: 'number', searchInParent: true, unit: '%', name: 'WASTE', required: false, defaultRole: 'value.waste', defaultUnit: '%'}, {role: /^value\.battery$/, indicator: false, write: false, type: 'number', searchInParent: true, unit: '%', name: 'BATTERY', required: false, defaultRole: 'value.battery', defaultUnit: '%'}, {role: /^value\.state$/, indicator: false, write: false, type: ['number', 'string'], searchInParent: true, name: 'STATE', required: false, defaultRole: 'value.state'}, {role: /^switch\.pause$/, indicator: false, write: true, type: 'boolean', searchInParent: true, name: 'PAUSE', required: false, defaultRole: 'switch.pause'}, {role: /^indicator(\.maintenance)?\.waste$|^indicator(\.alarm)?\.waste/, indicator: true, type: 'boolean', searchInParent: true, name: 'WASTE_ALARM', required: false, defaultRole: 'indicator.maintenance.waste'}, {role: /^indicator(\.maintenance)?\.water$|^indicator(\.alarm)?\.water/, indicator: true, type: 'boolean', searchInParent: true, name: 'WATER_ALARM', required: false, defaultRole: 'indicator.maintenance.water'}, {role: /^value(\.usage)?\.filter/, indicator: true, type: 'number', searchInParent: true, name: 'FILTER', required: false, defaultRole: 'value.usage.filter', defaultUnit: '%'}, {role: /^value(\.usage)?\.brush/, indicator: true, type: 'number', searchInParent: true, name: 'BRUSH', required: false, defaultRole: 'value.usage.brush', defaultUnit: '%'}, {role: /^value(\.usage)?\.sensors/, indicator: true, type: 'number', searchInParent: true, name: 'SENSORS', required: false, defaultRole: 'value.usage.sensors', defaultUnit: '%'}, {role: /^value(\.usage)?\.brush\.side/,indicator: true, type: 'number', searchInParent: true, name: 'SIDE_BRUSH', required: false, defaultRole: 'value.usage.brush.side', defaultUnit: '%'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.vacuumCleaner }, blinds: { states: [ {role: /^level(\.blind)?$/, indicator: false, type: 'number', write: true, enums: roleOrEnumBlind, name: 'SET', required: true, defaultRole: 'level.blind', defaultUnit: '%'}, // optional {role: /^value(\.blind)?$/, indicator: false, type: 'number', enums: roleOrEnumBlind, name: 'ACTUAL', required: false, defaultRole: 'value.blind', defaultUnit: '%'}, {role: /^button\.stop(\.blind)?$|^action\.stop$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'STOP', required: false, noSubscribe: true, defaultRole: 'button.stop.blind'}, {role: /^button\.open(\.blind)?$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'OPEN', required: false, noSubscribe: true, defaultRole: 'button.open.blind'}, {role: /^button\.close(\.blind)?$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'CLOSE', required: false, noSubscribe: true, defaultRole: 'button.close.blind'}, {role: /^level\.tilt$/, indicator: false, type: 'number', write: true, enums: roleOrEnumBlind, name: 'TILT_SET', required: false, defaultRole: 'level.open.tilt'}, {role: /^value\.tilt$/, indicator: false, type: 'number', enums: roleOrEnumBlind, name: 'TILT_ACTUAL', required: false, defaultRole: 'value.open.tilt'}, {role: /^button\.stop\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_STOP', required: false, noSubscribe: true, defaultRole: 'button.tilt.stop'}, {role: /^button\.open\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_OPEN', required: false, noSubscribe: true, defaultRole: 'button.tilt.open'}, {role: /^button\.close\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_CLOSE', required: false, noSubscribe: true, defaultRole: 'button.tilt.close'}, SharedPatterns.direction, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.blind }, blindButtons: { states: [ // blinds with no percentage setting / reading but buttons for up/down and stop: {role: /^button\.stop(\.blind)?$|^action\.stop$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'STOP', required: true, noSubscribe: true, defaultRole: 'button.blind.stop'}, {role: /^button\.open(\.blind)?$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'OPEN', required: true, noSubscribe: true, defaultRole: 'button.blind.open'}, {role: /^button\.close(\.blind)?$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'CLOSE', required: true, noSubscribe: true, defaultRole: 'button.blind.close'}, //optional tilt: {role: /^level\.tilt$/, indicator: false, type: 'number', write: true, enums: roleOrEnumBlind, name: 'TILT_SET', required: false, defaultRole: 'level.open.tilt'}, {role: /^value\.tilt$/, indicator: false, type: 'number', enums: roleOrEnumBlind, name: 'TILT_ACTUAL', required: false, defaultRole: 'value.open.tilt'}, {role: /^button\.stop\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_STOP', required: false, noSubscribe: true, defaultRole: 'button.tilt.stop'}, {role: /^button\.open\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_OPEN', required: false, noSubscribe: true, defaultRole: 'button.tilt.open'}, {role: /^button\.close\.tilt$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumBlind, name: 'TILT_CLOSE', required: false, noSubscribe: true, defaultRole: 'button.tilt.close'}, SharedPatterns.direction, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.blindButtons }, gate: { states: [ {role: /^switch(\.gate)?$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumGate, name: 'SET', required: true, defaultRole: 'switch.gate'}, // optional {role: /^value(\.position)?|^value(\.gate)?$/,indicator: false, type: 'number', enums: roleOrEnumGate, name: 'ACTUAL', required: false, defaultRole: 'value.blind', defaultUnit: '%'}, {role: /^button\.stop$|^action\.stop$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumGate, name: 'STOP', required: false, noSubscribe: true, defaultRole: 'button.stop'}, SharedPatterns.direction, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.maintain, SharedPatterns.error ], type: Types.gate }, weatherCurrent: { states: [ {role: /^value(\.temperature)?$/, indicator: false, type: 'number', name: 'ACTUAL', required: true, defaultRole: 'value.temperature', defaultUnit: '°C'}, {role: /^weather\.icon$/, indicator: false, name: 'ICON', required: true, defaultRole: 'weather.icon'}, // optional {role: /^value\.precipitation\.chance$/, indicator: false, type: 'number', name: 'PRECIPITATION_CHANCE', defaultRole: 'value.precipitation.chance', defaultUnit: '%'}, {role: /^value\.precipitation\.type$/, indicator: false, type: 'number', name: 'PRECIPITATION_TYPE', defaultRole: 'value.precipitation.type', defaultStates: {0: 'NO', 1: 'RAIN', 2: 'SNOW', 3: 'HAIL'}}, {role: /^value\.pressure$/, indicator: false, type: 'number', name: 'PRESSURE', defaultRole: 'value.pressure', defaultUnit: 'mbar'}, {role: /^value\.pressure\.tendency$/, indicator: false, type: 'string', name: 'PRESSURE_TENDENCY', defaultRole: 'value.pressure.tendency'}, {role: /^value\.temperature\.windchill$/, indicator: false, type: 'number', name: 'REAL_FEEL_TEMPERATURE', defaultRole: 'value.temperature.windchill', defaultUnit: '°C'}, {role: /^value.humidity$/, indicator: false, type: 'number', name: 'HUMIDITY', defaultRole: 'value.humidity', defaultUnit: '%'}, {role: /^value.uv$/, indicator: false, type: 'number', name: 'UV', defaultRole: 'value.uv'}, {role: /^weather\.state$/, indicator: false, type: 'string', name: 'WEATHER', defaultRole: 'weather.state'}, {role: /^value\.direction\.wind$/, indicator: false, type: 'string', name: 'WIND_DIRECTION', defaultRole: 'value.direction.wind', defaultUnit: '°'}, {role: /^value\.speed\.wind\.gust$/, indicator: false, type: 'number', name: 'WIND_GUST', defaultRole: 'value.speed.wind.gust', defaultUnit: 'km/h'}, {role: /^value\.speed\.wind$/, indicator: false, type: 'number', name: 'WIND_SPEED', defaultRole: 'value.speed.wind$', defaultUnit: 'km/h'}, SharedPatterns.lowbat, SharedPatterns.unreach, SharedPatterns.maintain, SharedPatterns.error ], type: Types.weatherCurrent }, camera: { states: [ {role: /^camera(\.\w+)?$/, indicator: false, type: 'file', name: 'FILE', required: true, defaultRole: 'camera'}, // optional {role: /^switch(\.camera)?\.autofocus$/, indicator: false, type: 'boolean', write: true, name: 'AUTOFOCUS', required: false, defaultRole: 'switch.camera.autofocus'}, {role: /^switch(\.camera)?\.autowhitebalance$/, indicator: false, type: 'boolean', write: true, name: 'AUTOWHITEBALANCE', required: false, defaultRole: 'switch.camera.autowhitebalance'}, {role: /^switch(\.camera)?\.brightness$/, indicator: false, type: 'boolean', write: true, name: 'BRIGHTNESS', required: false, defaultRole: 'switch.camera.brightness'}, {role: /^switch(\.camera)?\.nightmode$/, indicator: false, type: 'boolean', write: true, name: 'NIGHTMODE', required: false, defaultRole: 'switch.camera.nightmode'}, {role: /^level(\.camera)?\.position$|^level(\.camera)?(\.ptz)$/, indicator: false, type: 'number', write: true, name: 'PTZ', required: false, defaultRole: 'level.camera.position'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.camera, enumRequired: false }, lock: { states: [ {role: /^switch\.lock$/, indicator: false, type: 'boolean', write: true, name: 'SET', required: true, defaultRole: 'switch.lock'}, // optional {role: /^state$/, indicator: false, type: 'boolean', write: false, name: 'ACTUAL', required: false, defaultRole: 'state'}, { indicator: false, type: 'boolean', write: true, read: false, name: 'OPEN', required: false, noSubscribe: true, defaultRole: 'button'}, SharedPatterns.direction, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.lock }, motion: { states: [ {role: /^state\.motion$|^sensor\.motion$/, indicator: false, type: 'boolean', name: 'ACTUAL', required: true, defaultRole: 'sensor.motion'}, // optional {role: /brightness$/, indicator: false, type: 'number', name: 'SECOND', required: false, defaultRole: 'value.brightness', defaultUnit: 'lux'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.motion }, window: { states: [ {role: /^state(\.window)?$|^sensor(\.window)?/, indicator: false, type: 'boolean', enums: roleOrEnumWindow, name: 'ACTUAL', required: true, defaultRole: 'sensor.window'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.window }, windowTilt: { states: [ {role: /^state?$|^value(\.window)?$/, indicator: false, type: 'number', enums: roleOrEnumWindow, name: 'ACTUAL', required: true, defaultRole: 'value.window'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.windowTilt }, fireAlarm: { states: [ {role: /^state(\.alarm)?\.fire$|^sensor(\.alarm)?\.fire/, indicator: false, type: 'boolean', name: 'ACTUAL', required: true, channelRole: /^sensor(\.alarm)?\.fire$/, defaultRole: 'sensor.alarm.fire', defaultChannelRole: 'sensor.alarm.fire'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.fireAlarm, enumRequired: false }, floodAlarm: { states: [ {role: /^state(\.alarm)?\.flood$|^sensor(\.alarm)?\.flood/, indicator: false, type: 'boolean', name: 'ACTUAL', required: true, channelRole: /^sensor(\.alarm)?\.flood$/, defaultRole: 'sensor.alarm.flood', defaultChannelRole: 'sensor.alarm.flood'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.floodAlarm }, door: { states: [ {role: /^state?$|^state(\.door)?$|^sensor(\.door)?/, indicator: false, type: 'boolean', write: false, enums: roleOrEnumDoor, name: 'ACTUAL', required: true, defaultRole: 'sensor.door'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.door }, dimmer: { states: [ {role: /^level(\.dimmer)?$|^level\.brightness$/, indicator: false, type: 'number', write: true, enums: roleOrEnumLight, name: 'SET', required: true, defaultRole: 'level.dimmer', ignoreRole: /^level\.dimspeed$/, defaultUnit: '%'}, // optional {role: /^value(\.dimmer)?$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'ACTUAL', required: false, defaultRole: 'value.dimmer', defaultUnit: '%'}, {role: /^switch(\.light)?$|^state$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumLight, name: 'ON_SET', required: false, defaultRole: 'switch.light'}, {role: /^switch(\.light)?$|^state$/, indicator: false, type: 'boolean', write: false, enums: roleOrEnumLight, name: 'ON_ACTUAL', required: false, defaultRole: 'switch.light'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.dimmer }, light: { states: [ {role: /^switch(\.light)?$|^state$/, indicator: false, type: 'boolean', write: true, enums: roleOrEnumLight, name: 'SET', required: true, defaultRole: 'switch.light'}, // optional {role: /^switch(\.light)?$|^state$/, indicator: false, type: 'boolean', write: false, enums: roleOrEnumLight, name: 'ACTUAL', required: false, defaultRole: 'switch.light'}, {role: /^value\.power$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'ELECTRIC_POWER', required: false, defaultRole: 'value.power', defaultUnit: 'W'}, {role: /^value\.current$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'CURRENT', required: false, defaultRole: 'value.current', defaultUnit: 'mA'}, {role: /^value\.voltage$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'VOLTAGE', required: false, defaultRole: 'value.voltage', defaultUnit: 'V'}, {role: /^value\.power\.consumption$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'CONSUMPTION', required: false, defaultRole: 'value.power.consumption', defaultUnit: 'Wh'}, {role: /^value\.frequency$/, indicator: false, type: 'number', write: false, enums: roleOrEnumLight, name: 'FREQUENCY', required: false, defaultRole: 'value.frequency', defaultUnit: 'Hz'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.light }, volume: { states: [ {role: /^level\.volume$/, indicator: false, type: 'number', min: 'number', max: 'number', write: true, name: 'SET', required: true, defaultRole: 'level.volume'}, // optional {role: /^value\.volume$/, indicator: false, type: 'number', min: 'number', max: 'number', write: false, name: 'ACTUAL', required: false, defaultRole: 'value.volume'}, {role: /^media\.mute$/, indicator: false, type: 'boolean', write: true, name: 'MUTE', required: false, defaultRole: 'media.mute'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.volume }, location_one: { states: [ {role: /^value\.gps$/, indicator: false, type: 'string', write: false, name: 'GPS', required: true, defaultRole: 'value.gps'}, // optional {role: /^value\.gps\.elevation$/, indicator: false, type: 'number', write: false, name: 'ELEVATION', required: false, defaultRole: 'value.gps.elevation'}, {role: /^value\.radius$|value\.gps\.radius$/, indicator: false, type: 'number', write: false, name: 'RADIUS', required: false, defaultRole: 'value.gps.radius'}, {role: /^value\.accuracy$|^value\.gps\.accuracy$/, indicator: false, type: 'number', write: false, name: 'ACCURACY', required: false, defaultRole: 'value.gps.accuracy'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.location }, location: { states: [ {role: /^value\.gps\.longitude$/, indicator: false, type: 'number', write: false, name: 'LONGITUDE', required: true, defaultRole: 'value.gps.longitude', defaultUnit: '°'}, {role: /^value\.gps\.latitude$/, indicator: false, type: 'number', write: false, name: 'LATITUDE', required: true, defaultRole: 'value.gps.latitude', defaultUnit: '°'}, // optional {role: /^value\.gps\.elevation$/, indicator: false, type: 'number', write: false, name: 'ELEVATION', required: false, defaultRole: 'value.gps.elevation'}, {role: /^value\.radius$|value\.gps\.radius$/, indicator: false, type: 'number', write: false, name: 'RADIUS', required: false, defaultRole: 'value.gps.radius'}, {role: /^value\.accuracy$|^value\.gps\.accuracy$/, indicator: false, type: 'number', write: false, name: 'ACCURACY', required: false, defaultRole: 'value.gps.accuracy'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.location }, volumeGroup: { states: [ {role: /^level\.volume\.group?$/, indicator: false, type: 'number', min: 'number', max: 'number', write: true, name: 'SET', required: true, defaultRole: 'level.volume.group'}, // optional {role: /^value\.volume\.group$/, indicator: false, type: 'number', min: 'number', max: 'number', write: false, name: 'ACTUAL', required: false, defaultRole: 'value.volume.group'}, {role: /^media\.mute\.group$/, indicator: false, type: 'boolean', write: true, name: 'MUTE', required: false, defaultRole: 'media.mute.group'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.volumeGroup }, levelSlider: { states: [ {role: /^level(\..*)?$/, indicator: false, type: 'number', min: 'number', max: 'number', write: true, name: 'SET', required: true, defaultRole: 'level', defaultUnit: '%'}, // optional {role: /^value(\..*)?$/, indicator: false, type: 'number', min: 'number', max: 'number', write: false, name: 'ACTUAL', required: false, defaultRole: 'value'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.slider }, socket: { states: [ {role: /^switch$|^state$|^switch\.active$/, indicator: false, type: 'boolean', write: true, name: 'SET', required: true, defaultRole: 'switch'}, // optional {role: /^state$|^state\.active$/, indicator: false, type: 'boolean', write: false, name: 'ACTUAL', required: false, defaultRole: 'switch'}, {role: /^value\.power$/, indicator: false, type: 'number', write: false, name: 'ELECTRIC_POWER', required: false, defaultRole: 'value.power', defaultUnit: 'W'}, {role: /^value\.current$/, indicator: false, type: 'number', write: false, name: 'CURRENT', required: false, defaultRole: 'value.current', defaultUnit: 'mA'}, {role: /^value\.voltage$/, indicator: false, type: 'number', write: false, name: 'VOLTAGE', required: false, defaultRole: 'value.voltage', defaultUnit: 'V'}, {role: /^value\.power\.consumption$/, indicator: false, type: 'number', write: false, name: 'CONSUMPTION', required: false, defaultRole: 'value.power.consumption', defaultUnit: 'Wh'}, {role: /^value\.frequency$/, indicator: false, type: 'number', write: false, name: 'FREQUENCY', required: false, defaultRole: 'value.frequency', defaultUnit: 'Hz'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.socket }, button: { states: [ {role: /^button(\.[.\w]+)?$|^action(\.[.\w]+)?$/, indicator: false, type: 'boolean', read: false, write: true, name: 'SET', required: true, noSubscribe: true, defaultRole: 'button'}, // optional SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.button }, buttonSensor: { states: [ {role: /^button(\.[.\w]+)?$/, indicator: false, type: 'boolean', read: true, write: false, name: 'PRESS', required: true, defaultRole: 'button.press'}, // optional {role: /^button\.long/, indicator: false, type: 'boolean', read: true, write: false, name: 'PRESS_LONG', required: false, defaultRole: 'button.long'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.buttonSensor }, temperature: { states: [ {role: /temperature$/, indicator: false, write: false, type: 'number', name: 'ACTUAL', required: true, defaultRole: 'value.temperature', defaultUnit: '°C'}, {role: /humidity$/, indicator: false, write: false, type: 'number', name: 'SECOND', required: false, defaultRole: 'value.humidity', defaultUnit: '%'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.temperature }, humidity: { states: [ {role: /humidity$/, indicator: false, write: false, type: 'number', name: 'ACTUAL', required: true, defaultRole: 'value.humidity', defaultUnit: '%'}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.humidity }, image: { states: [ {role: /\.icon$|^icon$|^icon\.|\.icon\.|\.chart\.url\.|\.chart\.url$|^url.icon$/, indicator: false, write: false, type: 'string', name: 'URL', required: true}, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.image }, info: { states: [ { indicator: false, name: 'ACTUAL', required: true, multiple: true, noDeviceDetection: true, ignoreRole: /\.inhibit$/, defaultRole: 'state'}, SharedPatterns.working, SharedPatterns.unreach, SharedPatterns.lowbat, SharedPatterns.maintain, SharedPatterns.error ], type: Types.info } }; function checkEnum(obj, enums, words) { var found = false; if (enums) { enums.forEach(function (en) { var pos = en.lastIndexOf('.'); if (pos !== -1) { en = en.substring(pos + 1); } for (var lang in words) { if (words.hasOwnProperty(lang)) { if (words[lang].find(function (reg) { return reg.test(en); })) { found = true; return false; } } } }); } return found; } function roleOrEnum(obj, enums, roles, words) { if (roles && roles.indexOf(obj.common.role) !== -1) { return true; } return checkEnum(obj, enums, words); } // -------------- LIGHT ----------------------------------------- var lightWords = { en: [/lights?/i, /lamps?/i, /ceilings?/i], de: [/licht(er)?/i, /lampen?/i, /beleuchtung(en)?/i], ru: [/свет/i, /ламп[аы]/i, /торшеры?/, /подсветк[аи]/i, /лампочк[аи]/i, /светильники?/i] }; var lightRoles = ['switch.light', 'dimmer', 'value.dimmer', 'level.dimmer', 'sensor.light', 'state.light']; function roleOrEnumLight(obj, enums) { return roleOrEnum(obj, enums, lightRoles, lightWords); } // -------------- BLINDS ----------------------------------------- var blindWords = { en: [/blinds?/i, /windows?/i, /shutters?/i], de: [/rollladen?/i, /fenstern?/i, /beschattung(en)?/i], ru: [/ставни/i, /рольставни/i, /окна|окно/, /жалюзи/i] }; var blindRoles = ['blind', 'level.blind', 'value.blind', 'action.stop', 'button.stop', 'button.stop.blind', 'button.open.blind', 'button.close.blind']; function roleOrEnumBlind(obj, enums) { return roleOrEnum(obj, enums, blindRoles, blindWords); } // -------------- GATES ------------------------------------------ var gateWords = { en: [/gates?/i], de: [/toren/i, /tor/i], ru: [/ворота/i], }; var gateRoles = ['gate', 'value.gate', 'switch.gate', 'action.stop', 'button.stop']; function roleOrEnumGate(obj, enums) { return roleOrEnum(obj, enums, gateRoles, gateWords); } // -------------- WINDOWS ----------------------------------------- var windowRoles = ['window', 'state.window', 'sensor.window', 'value.window']; function roleOrEnumWindow(obj, enums) { return roleOrEnum(obj, enums, windowRoles, blindWords); } // -------------- DOORS ----------------------------------------- var doorsWords = { en: [/doors?/i, /gates?/i, /wickets?/i, /entry|entries/i], de: [/türe?/i, /tuere?/i, /tore?/i, /einfahrt(en)?/i, /pforten?/i], ru: [/двери|дверь/i, /ворота/i, /калитка|калитки/, /въезды?/i, /входы?/i] }; var doorsRoles = ['door', 'state.door', 'sensor.door']; function roleOrEnumDoor(obj, enums) { return roleOrEnum(obj, enums, doorsRoles, doorsWords); } this.getEnums = function () { return { door: { roles: doorsRoles, words: doorsWords }, window: { roles: windowRoles, words: blindWords, }, blind: { roles: blindRoles, words: blindWords, }, gate: { roles: gateRoles, words: gateWords }, light: { roles: lightRoles, words: lightWords } } } function getAllStatesInChannel(keys, channelId) { var list = []; var reg = new RegExp('^' + channelId.replace(/([$^.)([\]{}])/g, '\\$1') + '\\.[^.]+$'); keys.forEach(function (_id) { reg.test(_id) && list.push(_id); }); return list; } function getAllStatesInDevice(keys, channelId) { var list = []; var reg = new RegExp('^' + channelId.replace(/([$^.)([\]{}])/g, '\\$1') + '\\.[^.]+\\.[^.]+$'); keys.forEach(function(_id) { if (reg.test(_id)) list.push(_id); }); return list; } function getFunctionEnums(objects) { var enums = []; var reg = /^enum\.functions\./; for (var id in objects) { if (objects.hasOwnProperty(id) && reg.test(id) && objects[id] && objects[id].type === 'enum' && objects[id].common && objects[id].common.members && objects[id].common.members.length) { enums.push(id); } } return enums; } function getParentId(id) { var pos = id.lastIndexOf('.'); if (pos !== -1) { return id.substring(0, pos); } else { return id; } } this._applyPattern = function (objects, id, statePattern) { if (objects[id] && objects[id].common) { var role = null; if (!statePattern) { debugger; } if (statePattern.role) { role = statePattern.role.test(objects[id].common.role || ''); if (role && statePattern.channelRole) { var channelId = getParentId(id); if (objects[channelId] && (objects[channelId].type === 'channel' || objects[channelId].type === 'device')) { role = statePattern.channelRole.test(objects[channelId].common.role); } else { role = false; } } } if (role === false) { return false; } if (statePattern.objectType && objects[id].type !== statePattern.objectType) { return false; } if (statePattern.stateName && !statePattern.stateName.test(id)) { return false; } if (statePattern.unit && statePattern.unit !== objects[id].common.unit) { return false; } if (statePattern.ignoreRole && statePattern.ignoreRole.test(objects[id].common.role)) { return false; } if (statePattern.indicator === false && (objects[id].common.role || '').match(/^indicator(\.[.\w]+)?$/)) { return false; } if (statePattern.state && !statePattern.state.test(id.split('.').pop())) { return false; } if (statePattern.write !== undefined && statePattern.write !== (objects[id].common.write || false)) { return false; } if (statePattern.min === 'number' && typeof objects[id].common.min !== 'number') { return false; } if (statePattern.max === 'number' && typeof objects[id].common.max !== 'number') { return false; } if (statePattern.read !== undefined && statePattern.read !== (objects[id].common.read === undefined ? true : objects[id].common.read)) { return false; } if (statePattern.type) { if (typeof statePattern.type === 'string') { if (statePattern.type !== objects[id].common.type) { return false; } } else { var noOneOk = true; for (var t = 0; t < statePattern.type.length; t++) { if (statePattern.type[t] === objects[id].common.type) { noOneOk = false; break; } } if (noOneOk) { return false; } } } if (statePattern.enums && typeof statePattern.enums === 'function') { var enums = this._getEnumsForId(objects, id); if (!statePattern.enums(objects[id], enums)) { return false; } } return true; } else { return false; } }; this._getEnumsForId = function (objects, id) { this.enums = this.enums || getFunctionEnums(objects); var result = []; this.enums.forEach(function (e) { if (objects[e].common.members.indexOf(id) !== -1) { result.push(e); } }); if (!result.length && objects[id] && objects[id].type === 'state') { var channel = getParentId(id); if (objects[channel] && (objects[channel].type === 'channel' || objects[channel].type === 'device')) { this.enums.forEach(function (e) { if (objects[e].common.members.indexOf(channel) !== -1) { result.push(e); } }); } } return result.length ? result : null; }; function copyState(oldState, newState) { if (!newState) { newState = JSON.parse(JSON.stringify(oldState)); } if (newState instanceof Array) { for (var t = 0; t < newState.length; t++) { newState[t].original = oldState[t].original || oldState[t]; if (oldState[t].enums) { newState[t].enums = oldState[t].enums; } if (oldState[t].role) { newState[t].role = oldState[t].role; } if (oldState[t].channelRole) { newState[t].channelRole = oldState[t].channelRole; } if (oldState[t].icon) { newState[t].icon = oldState[t].icon; } } } else { newState.original = oldState.original || oldState; if (oldState.enums) { newState.enums = oldState.enums; } if (oldState.role) { newState.role = oldState.role; } if (oldState.channelRole) { newState.channelRole = oldState.channelRole; } if (oldState.icon) { newState.icon = oldState.icon; } } return newState; } this._testOneState = function (context) { var objects = context.objects; var pattern = context.pattern; var state = context.state; var channelStates = context.channelStates; var usedIds = context.usedIds; var _usedIds = context._usedIds; var ignoreIndicators = context.ignoreIndicators; var result = context.result; var found = false; channelStates.forEach(function (_id) { if ((state.indicator || (_usedIds.indexOf(_id) === -1 && (state.notSingle || usedIds.indexOf(_id) === -1))) && this._applyPattern(objects, _id, state)) { if (state.indicator && ignoreIndicators) { var parts = _id.split('.'); if (ignoreIndicators.indexOf(parts.pop()) !== -1) { console.log(_id + ' ignored'); return; } } if (!state.indicator){ _usedIds.push(_id); } if (!result) { context.result = result = JSON.parse(JSON.stringify(patterns[pattern])); result.states.forEach(function (state, j) { copyState(patterns[pattern].states[j], state); }); } if (!result.type) { debugger; } if (!result.states.find(function (e) {return e.id === _id;})) { var _found = false; for (var u = 0; u < result.states.length; u++) { if (result.states[u] instanceof Array) { for (var c = 0; c < result.states[u].length; c++) { if (result.states[u][c].name === state.name && result.states[u][c].role === state.role) { result.states[u] = result.states[u][c]; result.states[u].id = _id; _found = true; break; } } if (_found) { break; } } else { if (result.states[u].name === state.name) { result.states[u].id = _id; _found = true; break; } } } if (!_found) { console.error('Cannot find state for ' + _id); } } found = true; if (state.multiple && channelStates.length > 1) { // execute this rule for every state in this channel channelStates.forEach(function (cid) { if (cid === _id) return; if ((state.indicator || (_usedIds.indexOf(cid) === -1 && (state.notSingle || usedIds.indexOf(cid) === -1))) && this._applyPattern(objects, cid, state)) { if (!state.indicator) { _usedIds.push(cid); } var newState = copyState(state); newState.id = cid; result.states.push(newState); } }.bind(this)); } return false; // stop iteration } }.bind(this)); return found; }; function getChannelStates(objects, id, keys) { switch (objects[id].type) { case 'chart': case 'state': return [id]; case 'device': var result = getAllStatesInDevice(keys, id); if (result.length) { return result; } // if no states, it may be device without channels return getAllStatesInChannel(keys, id); default: // channel return getAllStatesInChannel(keys, id); } } function patternIsAllowed(pattern, allowedTypes, excludedTypes) { if (!pattern) { return false; } else if (allowedTypes && allowedTypes.indexOf(pattern.type) === -1) { return false; } else { return !excludedTypes || excludedTypes.indexOf(pattern.type) === -1; } } function allRequiredStatesFound(context) { if (!context.result) { return false; } var states = context.result.states; for (var a = 0; a < states.length; a++) { if (states[a] instanceof Array) { // one of for (var b = 0; b < states[a].length; b++) { if (states[a][b].required && states[a].id) { return true; } } return false; } else { if (states[a].required && !states[a].id) { return false; } } } return true; } this._detectNext = function (options) { var objects = options.objects; var id = options.id; var keys = options._keysOptional; var usedIds = options._usedIdsOptional; var ignoreIndicators = options.ignoreIndicators; if (!usedIds) { usedIds = []; options._usedIdsOptional = usedIds; } if (!objects[id] || !objects[id].common) { return null; } var context = { objects: objects, channelStates: getChannelStates(objects, id, keys), usedIds: usedIds, ignoreIndicators: ignoreIndicators }; for (var pattern in patterns) { if ( !patternIsAllowed( patterns[pattern], options.allowedTypes, options.excludedTypes ) ) { continue; } context.result = null; var _usedIds = []; context.pattern = pattern; context._usedIds = _usedIds; patterns[pattern].states.forEach(function (state) { var found = false; // one of following if (state instanceof Array) { for (var s = 0; s < state.length; s++) { context.state = state[s]; if (this._testOneState(context)) { found = true; break; } } if (!found) { context.result = null; return false; } } else { context.state = state; if (this._testOneState(context)) { found = true; } if (state.required && !found) { context.result = null; return false; } } }.bind(this)); if (!allRequiredStatesFound(context)) { continue; } _usedIds.forEach(function (id) { usedIds.push(id); }); var deviceStates; // looking for indicators and special states if (objects[id].type !== 'device') { // get device name var deviceId = getParentId(id); if ( objects[deviceId] && (objects[deviceId].type === 'channel' || objects[deviceId].type === 'device') ) { deviceStates = getAllStatesInDevice(keys, deviceId); if (deviceStates) { deviceStates.forEach(function (_id) { context.result.states.forEach(function (state, i) { if ( !state.id && (state.indicator || state.searchInParent) && !state.noDeviceDetection ) { if (this._applyPattern(objects, _id, state.original)) { context.result.states[i].id = _id; } } }.bind(this)); }.bind(this)); } } } context.result.states.forEach(function (state) { if (state.name.indexOf('%d') !== -1 && state.role && state.id) { var m = state.role.exec( context.objects[state.id].common.role ); if (m) { state.name = state.name.replace('%d', m[1]); } } if (state.role) { delete state.role; } if (state.enums) { delete state.enums; } if (state.original) { if (state.original.icon) { state.icon = state.original.icon; } delete state.original; } }); return context.result; } }; /** * detect * * Detect devices in some given path. Path can show to state, channel or device. * * @param options - parameters with following fields * objects - Object, that has all objects in form {'id1': {obj1params...}, 'id2': {obj2params...}} * id - Root ID from which the detection must start * _keysOptional - Array with keys from options.objects for optimization * _usedIdsOptional - Array with yet detected devices to do not similar device under different types * ignoreIndicators - If simple indicators like "low battery", "not reachable" must be detected as device or only as a part of other device. * allowedTypes - array with names of device types, that can be detected. Not listed device types will be ignored. * excludedTypes - array with names of device types, that must be ignored. The listed device types will be ignored. * @returns {*|boolean|"DIR"|"FILE"|ReadonlyArray<string>} */ this.detect = function (options) { var objects = options.objects; var id = options.id; var _keysOptional = options._keysOptional; var _usedIdsOptional = options._usedIdsOptional; // var ignoreIndicators = options.ignoreIndicators; if (this.cache[id] !== undefined) { return this.cache[id]; } if (!_keysOptional) { _keysOptional = Object.keys(objects); _keysOptional.sort(); options._keysOptional = _keysOptional; } if (_usedIdsOptional) { _usedIdsOptional = []; options._usedIdsOptional = _usedIdsOptional; } var result = []; var detected; while ((detected = this._detectNext(options))) { result.push(detected); } this.cache[id] = result.length ? result : null; return this.cache[id]; }; this.getPatterns = function () { var copyPatterns = {}; Object.keys(patterns).forEach(function (type) { var item = JSON.parse(JSON.stringify(patterns[type])); item.states.forEach(function (state, i) { if (patterns[type].states[i].role) { state.role = patterns[type].states[i].role.toString(); } if (patterns[type].states[i].enum) { state.enum = true; } }); copyPatterns[type] = item; }); return copyPatterns; }; (function _constructor(that) { that.enums = null; that.cache = {}; })(this); return this; } // Node.js if (typeof module !== 'undefined') { module.exports = { Types: Types, ChannelDetector: ChannelDetector }; module.exports.default = ChannelDetector; } else // ReactJS if (typeof exports !== 'undefined') { exports = { Types: Types, ChannelDetector: ChannelDetector }; exports.default = ChannelDetector; }